클래스를 설계도에, 인스턴스(객체)를 제품에 비유했던 것을 토대로 보면,
클래스 = 설계도
추상 클래스 = 미완성 설계도
여기서 미완성의 의미는 미완성 메서드(추상 메서드 = 구현부가 없는 메서드)를 포함하고 있다는 뜻
미완성 설계도 → 제품 (불가능)
미완성 설계도 → 설계도 → 제품 (가능)
이를 클래스에 대입하여 풀어보면 추상 클래스에서는 바로 인스턴스 생성이 불가능하고, 추상 클래스로부터 상속을 받은 자손 클래스에서 비로소 인스턴스 생성이 가능하다는 의미.
추상 클래스는 새로운 클래스 작성을 위한 뼈대와 바탕이 되는 조상 클래스로서 중요한 의미를 가짐
클래스 선언부에 abstract
키워드를 붙여 만들 수 있음
abstract class ClassName {
...
abstract void abstractMethod(); // 선언부만 있고 구현부가 없는 추상메서드
}
이제 abstract
키워드를 보면, 이 안에는 추상 메서드가 있으니 상속을 통해 구현해줘야 한다는 것을 알 수 있음
선언부만 있고 구현부는 없는 메서드
추상 클래스 안에서 이와 같이 추상 메서드를 남겨놓는 이유는 상속받는 클래스에 따라 구현부의 내용이 달라질 수 있는 것이기 때문.
메서드 선언부에 abstract
키워드를 붙여 만들 수 있음.
보통은 주석을 통해 설명을 붙여놓고 이후에 구현할 사람이 참고할 수 있게 해주는 것이 좋음.
abstract ReturnType MethodName();
추상 클래스로부터 상속 받은 자손 클래스는 오버라이딩을 통해 추상 메서드를 모두 구현할 의무가 있고,
만약 구현하지 않을 메서드가 있다면 자신도 추상 클래스로 지정해야함.
abstract class Ancestor {
abstract void absMethod1(int intVar);
abstract void absMethod2();
}
class Descendant1 extends Ancestor {
void absMethod1(int intVar) {
... // 작성
}
void absMethod2() {
... // 작성
}
} // 추상 메서드를 모두 구현했으므로 OK
abstract class Descendant2 extends Ancestor {
void absMethod2() {
... // 작성
}
} // 추상 메서드를 전부 구현하지는 못했으므로 클래스 앞에 abstract를 붙여주어야함
상속이 자손 클래스를 만드는 것이라고 한다면, 추상화는 조상 클래스를 만드는 것이라고 할 수 있음
추상화: 클래스 간의 공통점을 찾아내어 공통의 조상을 만드는 작업
구체화: 상속을 통해 클래스를 구현, 확장하는 작업
abstract class Mammal {
abstract void move();
abstract void nourish();
void breed() {
... // 짝짓기를 통해 새끼를 낳아서 번식함
};
}
class Lion extends Mammal {
move() {
... // 네 발로 움직임
}
nourish() {
... // 동물을 먹음 (육식성)
}
}
class Whale extends Mammal {
move() {
... // 지느러미로 헤엄쳐서 움직임
}
nourish() {
... // 동물을 먹음 (육식성)
}
}
class Kangaroo extends Mammal {
move() {
... // 두 발로 움직임
}
nourish() {
... // 식물을 먹음 (초식성)
}
}
Q. 추상 클래스 내의 추상 메서드를 굳이 abstract
붙여가며 추상 메서드로 놓지 말고, 일반 메서드처럼 하되 구현부 {}
만 비워놓아도 되지 않나?
어차피 자손 클래스에서 오버라이딩 하면 결과는 같다는 점에서 의문이 들 수 있음
A: 일반 메서드를 오버라이딩 하는 것은 선택 사항이지만, 추상 메서드를 구현하는 것은 자손 클래스에서 반드시 해야하는 것이므로 강제성을 부여하기 위해 추상 메서드를 사용함.
여기서 동물 객체들을 공통 타입인 Mammal
로 묶어 배열로 만들고, 공통 메서드를 호출하는 것도 가능함
Mammal[] mammalGroup = new Mammal[]{new Lion(), new Whale(), new Kangaroo()};
for (int i = 0; i < mammalGroup.length; i++) {
mammalGroup[i].nourish(); // 조상 타입의 참조변수로 공통 메서드를 호출했음
}
이 때 호출되는 공통 메서드는 추상 클래스로부터 나오는 추상 메서드가 아닌, 각 자식 클래스에서 개별적으로 구현된 메서드임.