해당 게시물은 자바의 정석을 정리한 내용 입니다.
6.1. 추상 클래스(abstract class)
추상 클래스
- 추상 클래스는 추상 메서드(미완성 메서드)를 포함하고 있는 클래스이다.
abstract class 클래스이름{
...
}
abstract class Player { // 추상 클래스(미완성 클래스)
abstract void play(int pos); // 추상 메서드(몸통이 없는 미완성 메서드)
abstract void stop(); // 추상 메서드
}
- 다른 클래스를 작성하는데 도움을 주기 위한 것이며 인스턴스 생성 불가
Player p = new Player(); // 에러. 추상 클래스의 인스턴스 생성 불가
- 추상 클래스는 상속을 통해 추상 메서드를 완성해야 인스턴스 생성이 가능하다.
class AudioPlayer extends Player{ // Player : 추상 클래스
void play(int pos) { /* 내용 생략 */ } // 추상 메서드를 구현
void stop() { /* 내용 생략 */ } // 추상 메서드를 구현
}
AudioPlayer ap = new AudioPlayer(); // 인스턴스 생성 OK.
* 추상 클래스를 구현한다는 것은 추상 메서드의 몸통을 만들어주는 것을 말한다.
6.2 추상 메서드(abstract method)
추상 메서드?
- 추상 메서드는 선언부만 있고 구현부가 없는 미완성 메서드이다.
/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다. */
abstract 리턴타입 메서드이름();
- 꼭 필요하지만 자손 마다 다르게 구현될 것으로 예상되는 경우에 사용된다.
6.3 추상 클래스의 작성
여러 클래스에 공통적으로 사용될 수 있는 추상 클래스를 바로 작성하거나 기존 클래스의 공통 부분을 뽑아서
추상 클래스를 만든다.
추상화 : 클래스 간의 공통점을 찾아내서 공통의 조상을 만드는 작업
구체화 : 상속을 통해 클래스를 구현, 확장하는 작업
아래에 Player라는 추상 클래스를 작성해 보았다.
이 클래스는 VCR이나 Audio와 같은 재생 가능한 기기(Player)를 클래스로 작성할 때, 이 들의 조상으로 사용 될 수 있을 것이다.
abstract class Player{
boolean pause; // 일시정지 상태를 저장하기 위한 변수
int currentPos; // 현재 Play되고 있는 위치를 저장하기 위한 변수
Player() { // 추상 클래스도 생성자가 있어야 한다.
pause = false;
currentPos = 0;
}
/* 지정된 위치(pos)에서 재생을 시작하는 기능이 수행되도록 작성되어야 한다. */
abstract void play(int pos); // 추상 메서드
/* 재생을 즉시 멈추는 기능을 수행하도록 작성되어야 한다. */
abstract void stop(); // 추상 메서드
void play(){
play(currentPos); // 추상 메서드를 사용할 수 있다.
}
void pause(){
if(pause){
pause = false;
play(currentPos);
}else{
pause = true;
stop();
}
}
}
이제 Player 클래스를 조상으로 하는 CDPlayer 클래스를 만들어 보자.
조상 클래스의 추상 메서드를 CDPlayer 클래스의 기능에 맞게 완성해주고, CDPlayer 만의 새로운 기능들을 추가하였다.
class CDPlayer extends Player{
void play(int currentPos){
/* 조상의 추상 메서드를 구현. 내용 생략 */
}
void stop(){
/* 조상의 추상 메서드를 구현. 내용 생략 */
}
// CDPlayer 클래스에 추가로 정의된 멤버
int currentTrack; // 현재 재생 중인 트랙
void nextTrack(){
currentTrack++;
...
}
void preTrack(){
if(currentTrack > 1){
currentTrack--;
}
...
}
}
이번엔 기존의 클래스로 부터 공통된 부분을 뽑아내어 추상 클래스를 만들어 보도록 하자.
class Marine{ // 보병
int x, y; // 현재 위치
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stop(){ /* 현재 위치에 정지 */ }
void stimPack(){ /* 스팀팩을 사용한다. */ }
}
class Tank{ // 탱크
int x, y; // 현재 위치
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stop(int x, int y){ /* 현재 위치에 정지 */ }
void changeMode(int x, int y){ /* 공격 모드를 변환한다. */ }
}
class Dropship{ // 수송선
int x, y; // 현재 위치
void move(int x, int y){ /* 지정된 위치로 이동 */ }
void stop(){ /* 현재 위치에 정지 */ }
void load(){ /* 선택된 대상을 태운다, */ }
void unload(){ /* 선택된 대상을 내린다, */ }
}
아래 코드는 각 클래스의 공통 부분을 뽑아내서 Unit 클래스를 정의하고 이로부터 상속 받도록 하였다.
이 Unit 클래스는 다른 유닛을 위한 클래스를 작성하는데 재활용될 수 있을 것이다.
abstract class Unit{
int x, y;
abstract void move(int x, int y);
void stop() { /* 현재 위치에 정지 */ }
}
class Marine extends Unit{
void move(int x, int y) { /* 지정된 위치로 이동 */ }
void stimPack() { /* 스팀팩을 사용한다. */ }
}
class Tank extends Unit{
void move(int x, int y) { /* 지정된 위치로 이동 */ }
void changeMode() { /* 공격모드를 변환한다. */ }
}
class Dropship extends Unit{
void move(int x, int y) { /* 지정된 위치로 이동 */ }
void load() { /* 선택된 대상을 태운다. */ }
void unload() { /* 선택된 대상을 내린다. */ }
}
그리고 공통 조상인 Unit 클래스 타입의 참조변수 배열을 통해서 서로 다른 종류의 인스턴스를 하나의 묶음으로 다룰 수 있다.
Unit 클래스에 move 메서드가 추상 메서드로 정의 되어 있다 하더라도 이처럼 Unit 클래스 타입의 참조 변수로 move 메서드를 호출하는 것이 가능하다.
메서드는 참조 변수의 타입에 관계 없이 실제 인스턴스에 구현된 것이 호출되기 때문이다.
Unit[] group = new Unit[4];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new Marine();
group[3] = new Dropship();
// 다음과 같이, 배열의 생성과 초기화를 한번에 할 수도 있다.
// Unit[] group = { new Marine(), new Tank(), new Dropship() , new Marine() };
for(int i=0; i<group.length; i++)
group[i].move(100, 200); // 각 객체의 추상 메서드를 구현한 메서드가 호출 됨
7.1 인터페이스란?
인터페이스(interface)
- 인터페이스는 추상 메서드의 집합이다.
- 추상 메서드와 상수만을 멤버로 가질 수 있다.
(JDK 1.8 부터 인터페이스는 디폴트 메서드, static 메서드를 가질 수 있다.)
- 인스턴스를 생성할 수 없고, 다른 클래스 작성에 도움을 줄 목적으로 사용된다.
추상 클래스와 인터페이스의 차이점
① 추상 클래스
- 추상 클래스는 클래스 내 추상 메서드가 하나 이상 포함되거나 abstract로 정의된 경우를 말한다.
- 단일 상속만 가능하며 일반 변수를 가질 수 있다.
- 추상 클래스는 상속을 받아서 그 기능을 확장 시키기 위해 사용된다.
② 인터페이스
- 인터페이스는 모든 메서드가 추상 메서드인 경우를 말한다.
(JDK 1.8 부터 디폴트 메서드, static 메서드를 가질 수 있다.)
- 다중 상속이 가능하며 일반 변수를 가질 수 없다.
- 해당 인터페이스를 구현하는 모든 클래스에 대해 같은 동작을 하도록 하기 위해 사용된다.
7.2 인터페이스의 작성
- class 대신 interface를 사용한다는 것 외에는 클래스 작성과 동일하다.
interface 인터페이스이름{
public static final 타입 상수명 = 값;
public abstract 메서드명(매개변수 목록);
}
- 인터페이스의 멤버들은 다음과 같은 제약사항이 있다.
① 모든 멤버 변수는 public static final 이어야 하며, 이를 생략할 수 있다.
② 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
단, static 메서드와, 디폴트 메서드는 예외 (JDK 1.8 부터)
7.3 인터페이스의 상속
- 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중 상속을 허용한다.
→ 상속 받는 메서드의 선언부가 같고 내용이 다르면 어느 쪽을 상속 받을지 결정 할 수 없다.
하지만 인터페이스는 추상메서드의 집합이며 추상 메서드는 몸통이 없기 때문에 인터페이스는 충돌 문제가 없다.
이러한 이유로 다중 상속을 허용한다.
interface Movable{
/* 지정된 위치(x, y)로 이동하는 기능의 메서드 */
void move(int x, int y);
}
interface Attackable{
/* 지정된 대상을 공격하는 기능의 메서드 */
void attack(Unit u);
}
interface Fightable extends Movable, Attackable{ } // 다중 상속을 허용
- 인터페이스는 Object 클래스와 같은 최고 조상이 없다.
7.4 인터페이스의 구현
- 인터페이스를 구현하는 것은 인터페이스에 정의된 추상 메서드를 완성하는 것이다.
- 키워드 'implements'를 사용한다.
class 클래스이름 implements 인터페이스이름{
// 인터페이스에 정의된 추상 메서드를 구현 해야 한다.
}
- 인터페이스에 정의된 모든 추상 메서드를 완성해야 한다.
interface Fightable{
void move(int x, int y);
void attack(Unit u);
}
class Fighter implements Fightable{ // Fighter 클래스는 Fightable 인터페이스를 구현했다.
public void move() { /* 내용 생략 */ }
public void attack() { /* 내용 생략 */ }
}
- 상속과 구현이 동시에 가능하다.
class Fighter extends Unit implements Fightable{
public void move(int x, int y) { /* 내용 생략 */ }
public void attack(Unit u) { /* 내용 생략 */ }
}
7.5 인터페이스를 이용한 다형성
- 인터페이스 타입의 참조 변수로 해당 인터페이스를 구현한 클래스의 인스턴스를 참조 할 수 있다.
- 예를 들어, Fightable 인터페이스가 다음과 같이 정의 되어 있다.
interface Fightable {
void move(int x, int y);
void attack(Fightable f);
}
- Fighter 클래스가 Fightable 인터페이스를 구현했을 때, 다음과 같이 Fightable 타입의 참조 변수로 Fighter 인스턴스를
참조하는 것이 가능하다.
class Fighter extends Unit implements Fightable{
public void move(int x, int y) { /* 내용 생략 */ }
public void attack(Fightable f) { /* 내용 생략 */ }
}
Unit u = new Fighter(); // 조상 클래스 : 자손 객체
Fightable f = new Fighter(); // 인터페이스 : 인터페이스 구현 객체
- 대신 Fightable 인터페이스에 정의된 2개의 멤버(move, attack 메서드)만 사용 가능하다.
f.move(100, 200);
f.attack(new Fighter());
- 메서드의 매개변수 타입으로 인터페이스를 지정할 수 있다.
매개변수의 타입이 인터페이스라는 것은 매개변수로 해당 인터페이스를 구현한 클래스의 인스턴스만 가능하다는 것을 의미한다.
void attack(Fightable f){
// . . .
}
- 메서드의 리턴 타입으로 인터페이스를 지정할 수 있다.
리턴 타입이 인터페이스라는 것은 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.
Fightable method(){
Fighter f = new Fighter();
return f;
}
7.6 인터페이스의 이해
- 인터페이스는 두 대상(객체) 간의 '연결, 대화, 소통'을 돕는 '중간 역할'을 한다.
- 선언(설계)과 구현을 분리 시킬 수 있게 한다.
예를 들어, 다음과 같이 B 클래스가 정의 되어 있을 때는 변경에 불리하다. (유연하지 못함)
class B {
public void method() {
System.out.println("methodInB");
}
}
그래서 아래 코드처럼 B 클래스를 인터페이스를 이용하여 선언과 구현을 분리 할 수 있다.
그러면 변경에 유리해지며 유연한 코드가 된다.
interface I { // 선언
public void method();
}
class B implements I { // 구현
public void method(){
System.out.println("methodInB");
}
}
- 인터페이스 덕분에 B가 다른 것으로 변경 되더라도 A를 변경하지 않아도 된다. (느슨한 결합)
7.7 인터페이스의 장점
① 개발 시간을 단축 할 수 있다.
② 변경에 유리한 유연한 설계가 가능하다.
③ 표준화가 가능하다.
Ex) JDBC (인터페이스 집합)
→ Java 애플리케이션이 JDBC를 사용하면 데이터베이스를 다른 것으로 변경하더라도 애플리케이션을 변경하지
않아도 됨
④ 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
class RepairableTest{
public static void main(String[] args) {
Tank tank = new Tank();
Dropship dropship = new Dropship();
Marine marine = new Marine();
SCV scv = new SCV();
scv.repair(tank); // SCV가 Tank를 수리하도록 한다.
scv.repair(dropship);
// scv.repair(marine);
}
}
interface Repairable {}
class GroundUnit extends Unit {
GroundUnit(int hp) {
super(hp);
}
}
class AirUnit extends Unit {
AirUnit(int hp) {
super(hp);
}
}
class Unit {
int hitPoint;
final int MAX_HP;
Unit(int hp) {
MAX_HP = hp;
}
//...
}
class Tank extends GroundUnit implements Repairable {
Tank() {
super(150); // Tank의 HP는 150이다.
hitPoint = MAX_HP;
}
public String toString() {
return "Tank";
}
//...
}
class Dropship extends AirUnit implements Repairable {
Dropship() {
super(125); // Dropship의 HP는 125이다.
hitPoint = MAX_HP;
}
public String toString() {
return "Dropship";
}
//...
}
class Marine extends GroundUnit {
Marine() {
super(40);
hitPoint = MAX_HP;
}
//...
}
class SCV extends GroundUnit implements Repairable{
SCV() {
super(60);
hitPoint = MAX_HP;
}
void repair(Repairable r) { // 매개변수로 Repairable 인터페이스를 구현한 클래스의 객체만 가능
if (r instanceof Unit) {
Unit u = (Unit)r;
while(u.hitPoint!=u.MAX_HP) {
/* Unit의 HP를 증가시킨다. */
u.hitPoint++;
}
System.out.println( u.toString() + "의 수리가 끝났습니다.");
}
}
//...
}
7.8 디폴트 메서드와 static 메서드
- JDK 1.8 부터 인터페이스에 디폴트 메서드, static 메서드를 추가 할 수 있도록 변경 되었다.
- 인터페이스에 새로운 메서드(추상 메서드)를 추가하는 것은 어렵다.
왜냐하면 해당 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현 해야 하기 때문이다.
이러한 문제점을 해결하기 위해 디폴트 메서드(default method)를 고안해 내었다.
- 디폴트 메서드는 인터페이스에 추가된 일반 메서드이다. (기존의 인터페이스 원칙 위반)
앞에 키워드 default를 붙이며 일반 메서드처럼 몸통{}이 있어야 한다. 그리고 접근 제어자가 public이며 생략 가능하다.
interface MyInterface{
void method();
default void newMethod(){}
}
- 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌할 때의 해결책
① 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
② 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
위의 규칙이 외우기 힘들면 "그냥 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 하면 된다."
8.1 내부 클래스(Inner class)란?
내부 클래스
- 내부 클래스는 클래스 내에 선언된 클래스이다.
class A { // 외부 클래스
...
class B { // 내부 클래스
...
}
...
}
- 내부 클래스의 장점
① 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
② 코드의 복잡성을 줄일 수 있다. (캡슐화)
8.2 내부 클래스의 종류와 특징
- 내부 클래스의 종류와 유효범위(scope)는 변수와 동일
내부 클래스 | 특 징 |
인스턴스 내부 클래스 | 외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다루어진다. |
스태틱 내부 클래스 | 외부 클래스의 멤버변수 선언 위치에 선언하며, 외부 클래스의 static 멤버처럼 다루어진다. |
지역 내부 클래스 | 외부 클래스의 메서드나 초기화 블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있다. |
익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스이다. (일회용) |
8.3 내부 클래스의 제어자와 접근성
- 내부 클래스의 제어자는 변수에 사용 가능한 제어자와 동일하다.
- 내부 클래스도 클래스이기 때문에 abstract나 final과 같은 제어자를 사용 할 수 있으며 멤버 변수처럼 접근제어자 4개를
모두 사용 할 수 있다.
class InnerEx1 {
class InstanceInner { // 인스턴스 내부 클래스
int iv = 100;
// static int cv = 100; // 에러! static변수를 선언할 수 없다.
final static int CONST = 100; // final static은 상수이므로 허용한다.
}
static class StaticInner { // static 내부 클래스
int iv = 200;
static int cv = 200; // static 내부 클래스만 static 멤버를 정의할 수 있다.
}
void myMethod() {
class LocalInner { // 지역 내부 클래스
int iv = 300;
// static int cv = 300; // 에러! static변수를 선언할 수 없다.
final static int CONST = 300; // final static은 상수이므로 허용
}
}
public static void main(String args[]) {
System.out.println(InstanceInner.CONST);
System.out.println(StaticInner.cv);
}
}
8.4 익명 클래스(anonymous class)
- 익명 클래스는 클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스이다.
new 조상클래스명() {
// 멤버 선언
}
또는
new 구현인터페이스명() {
// 멤버 선언
}
class InnerEx6 {
Object iv = new Object(){ void method(){} }; // 익명클래스
static Object cv = new Object(){ void method(){} }; // 익명클래스
void myMethod() {
Object lv = new Object(){ void method(){} }; // 익명클래스
}
}
'Java > 객체 지향 개념 ~' 카테고리의 다른 글
자바의 정석 (Chapter 9. java.lang 패키지와 유용한 클래스) (0) | 2020.08.14 |
---|---|
자바의 정석 (Chapter 8. 예외 처리) (1) | 2020.08.08 |
자바의 정석 (Chapter 7_2. 객체지향개념 2) (0) | 2020.08.02 |
자바의 정석 (Chapter 7_1. 객체지향개념 2) (0) | 2020.08.02 |
자바의 정석 (Chapter 6_3. 객체지향개념 1) (0) | 2020.08.01 |
댓글