3장 내용은 총 2편으로 글을 올리려고 했는데 이번 상속 부분이 양이 많고 좀 더 자세히 정리하고 싶어서 3장은 총 3편으로 구성이 될 것 같다.
상속이란 단어는 영어 단어를 그대로 옮기면서 생긴 오해이다. 객체 지향에서 상속은 재사용과 확장으로 이해하는 것이 맞다.
상속 = 재사용 + 확장
상속 관계에서 반드시 만족해야 할 문장이 있다.
"하위 클라스는 상위 클라스이다" 이다.
객체 지향 상속에 있어서 아주 중요한 문장이다. 이를 그림1 조직도에 작용을 한다면
[그림 1] 객체 지향의 상속에 대한 잘못된 예 - 계층도 / 조직도
말이 되지 않는다. 이번엔 그림2 분류도의 경우를 살펴보자.
[그림 2] 객체 지향의 상속에 대한 올바른 예 - 분류도
너무나도 자연스럽다. 이처럼 객체 지향에서 상속이란 일반적으로 생각하는 상속이 아닌 확장, 세분화의 개념으로 이해하는 것이 바람직하다. 자바 또한 inheritance(상속)이라는 키워드 대신 extends(확장)이라는 키워드를 사용한다.
그림2의 분류도를 자바 코드로 만들어 보자.
package sec06.exam01;
public class 생물 {
String myClass;
생물(){
myClass = "생물";
}
void showMe() {
System.out.println(myClass);
}
}
[그림 3.1] 생물.java
package sec06.exam01;
public class 동물 extends 생물 {
동물(){
myClass = "동물";
}
}
[그림 3.2] 동물.java
package sec06.exam01;
public class 식물 extends 생물 {
식물(){
myClass = "식물";
}
}
[그림 3.3] 식물.java
package sec06.exam01;
public class 참새 extends 동물 {
참새(){
myClass = "참새";
}
}
[그림 3.4] 참새.java
package sec06.exam01;
public class 개구리 extends 동물 {
개구리(){
myClass = "개구리";
}
}
[그림 3.5] 개구리.java
package sec06.exam01;
public class 행운목 extends 식물 {
행운목(){
myClass = "행운목";
}
}
[그림 3.6] 행운목.java
package sec06.exam01;
public class 뱅갈고무나무 extends 식물 {
뱅갈고무나무(){
myClass = "뱅갈고무나무";
}
}
[그림 3.7] 뱅갈고무나무.java
이어서 main() 메서드를 가진 테스트용 클라스를 만들어보자.
package sec06.exam01;
public class Driver01 {
public static void main(String[] args) {
생물 creature = new 생물();
동물 animal = new 동물();
식물 plant = new 식물();
참새 sparrow = new 참새();
개구리 frog = new 개구리();
행운목 fortune = new 행운목();
뱅갈고무나무 rubber = new 뱅갈고무나무();
creature.showMe();
animal.showMe();
plant.showMe();
sparrow.showMe();
frog.showMe();
fortune.showMe();
rubber.showMe();
}
}
[그림 3.8] Driver01.java
상위 클래스에서만 showMe() 메서드를 구현했지만 모든 하위 클래스의 객체에서 showMe() 메서드를 사용할 수 있다.(재사용) 상속한다는 것은 이렇게 상위 클래스의 특성을 상속하다는 의미이지 부모-자식 관계는 아니다. 두번째 코드인 그림3.9 를 살펴보자.
package sec06.exam01;
public class Driver02 {
public static void main(String[] args) {
생물 creature = new 생물();
생물 animal = new 동물();
생물 plant = new 식물();
생물 sparrow = new 참새();
생물 frog = new 개구리();
생물 fortune = new 행운목();
생물 rubber = new 뱅갈고무나무();
creature.showMe();
animal.showMe();
plant.showMe();
sparrow.showMe();
frog.showMe();
fortune.showMe();
rubber.showMe();
}
}
[그림 3.9] Driver02.java
실행 결과는 Driver01과 같다. "하위 클래스는 상위 클래스이다"라는 말이 코드에 잘 표현 되어있다. 동물은 생물이다, 개구리는 생물이다, 뱅갈고무나무는 생물이다 라는 표현에 이상함이 없다. 이처럼 객체 지향은 현실 세계를, 인간의 논리를 그대로 코드로 옮길 수 있는 힘이 있다.
먼저 자바에서는 다중 상속을 지원하지 않는다. 그 이유를 살펴보자.
[그림 4] 다중 상속의 문제점
베오울프를 가지고 예를 들어보자. 여기서 베오울프는 곰과 늑대를 상속한다고 생각해보자. 곰과 늑대는 둘다 울수가 있다.(cry 말고 howling) 베오울프에게 울라고 했을때, 곰 처럼 울어야 하는가 늑대처럼 울어야 하는가? 이와 같은 문제를 다중 상속의 다이아몬드 문제라고 한다. 때문에 자바는 다중 상속을 버리고 인터페이스를 도입했다.
메모리를 살펴보기 위해 그림 5와 같은 클래스를 생성하였다.
[그림 5] Animal.java & Dog.java
그리고 main() 메서드가 있는 Driver.java도 만들었다.
[그림 6] Driver.java
8번째 줄을 실행한 후 메모리 구조를 그려보면 그림 7과 같다.
[그림 7] 8번째 줄 실행 후 메모리
먼저 하위 클래스의 인스턴스가 생성되면 상위 클래스의 인스턴스도 함께 생성되기 때문에 Heap 영역에 Dog 클래스의 인스턴스 뿐만 아니라 Animal 클래스의 인스턴스도 함깨 생성된 것을 확인할 수 있다.
[그림 8] 18번째 줄 실행 후 메모리
늙은이 객체 참조 변수가 가리키고 있는 것은 Dog 인스턴스가 아닌 Animal 인스턴스이다. 이 부분이 코드에 어떻게 나와 있었는지 5번째 줄과 15번째 줄을 유심히 살펴보자.
Dog 젊은이 = new Dog();
Animal 늙은이 = new Dog();
늙은이 객체 참조 변수는 사실 개이지만 자신이 개라는 사실을 모르고 있다. 다만 자신이 동물이라는 것은 인식하고 있다. 따라서 늙은이 객체 변수는 16번째 줄에 나타난 개의 서식지 속성과 19번째 줄에 나타난 개의 서식지를 알려주는 showHabitat() 메서드를 사용할 수 없다.