상속은 가족관계와 같은 계층도나 조직도로 이해하면 안된다.
상속은 아래 그림과 같은 분류도로 이해해야 한다.
그림을 보면 동물과 포유류, 어류는 상속 관계가 아니다. 마찬가지로 포유류와 개, 판다도 상속 관계가 아니다.
따라서 상속을 '부모 클래스 - 자식 클래스' 라는 표현 보다는 '상위 클래스 - 하위 클래스' 혹은 '슈퍼 클래스 - 서브 클래스' 라고 표현하는 것이 이해하기 더 쉽다. (벤다이어 그램을 떠올리면 이해하기 쉽다.)
그러므로 우리가 흔히 생각하는 '상속'이 아닌 확장, 세분화, 슈퍼클래스 - 서브클래스 개념으로 이해하자.
상위 클래스 쪽으로 갈 수록 추상화(Abstract), 일반화(Generalization) 되었다 말하며
하위 클래스 쪽으로 갈 수록 구체화(Concrete), 특수화(Specialization) 되었다 말한다.
이는 상속 관계에서 반드시 만족해야하는 문장이며 객체 지향 설계 5원칙 가운데 LSP(리스코프 치환 원칙)를 나타내는 말이다.
위의 동물 분류도를 우리가 흔히 아는 상속의 개념처럼 가족 계층도로 바꾸어 생각해보자.
할아버지 / 아버지 삼촌 / 아들 딸 사촌형 사촌누나 와 같읕 예시를 생각할 수 있다.
이제 이를 활용하여 '하위 클래스는 상위 클래스'라는 문장에 대입해 보자.
자바 언어에서 inheritance(상속)라는 키워드는 존재하지 않는다.
대신 extends(확장) 이라는 키워드가 존재한다.
실전에서 클래스명은 클래스답게, 객체 참조 변수명은 객체답게 짓자.
즉, 클래스명은 분류스럽게 객체 참조 변수명은 유일무이한 사물처럼 작명하자.
객체 참조 변수는 객체를 참조해야 되는데 객체의 특성은 유일무이한 것이기 때문이다.
어류 fish = new 어류(); 이런식으로 작명하는 것은 좋지 못함
⬇️ 예시 코드
package 상속01
public class 동물 {
String className;
동울() {
className = "동물";
}
void show() {
System.out.println(className);
}
}
----------------------------------------------
package 상속01
public class 포유류 extends 동물 {
포유류() {
className = "포유류";
}
}
----------------------------------------------
public class 어류 extends 동물 {
어류() {
className = "어류";
}
}
public class 판다 extends 포유류 {
판다() {
className = "판다";
}
}
----------------------------------------------
package 상속01
public class 넙치 extends 어류 {
넙치() {
className = "넙치";
}
}
----------------------------------------------
package 상속01
public class 상속예시01 {
public static void main(String[] args) {
동물 animal = new 동물();
포유류 mammalia = new 포유류();
어류 fish = new 어류();
판다 panda = new 판다();
넙치 halibut = new 넙치();
animal.show();
mammalia.show();
fish.show():
panda.show();
halibut.show();
}
}
상위 클래스에서만 show( ) 메서드를 구현했지만 하위 클래스의 객체들에서도 show( ) 메서드를 사용헐 수 있다.
상속한다는 것은 이렇게 상위 클래스의 특성을 상속한다는 의미이다. (부모-자식 관계가 아님)
package 상속01
public class 상속예시02 {
public static void main(String[] args) {
동물 animal = new 동물();
동물 mammalia = new 포유류();
동물 fish = new 어류();
동물 panda = new 판다();
동물 halibut = new 넙치();
animal.show();
mammalia.show();
fish.show():
panda.show();
halibut.show();
}
}
상속예시02의 코드는 상속예시01과 같은 결과를 출력한다.
"하위 클래스는 상위 클래스" 라는 말이 위와 같은 코드로 표현되었다.
포유류, 어류, 판다, 넙치는 모두 동물이다.
package 상속01
public class 상속예시03 {
public static void main(String[] args) {
동물[] animals = new 동물[5]
animals[0] = new 동물();
animals[1] = new 포유류();
animals[2] = new 어류();
animals[3] = new 판다();
animals[4] = new 넙치();
for (int i=0 ; i<animals.length ; i++) {
animals[i].show();
}
}
}
클래스 상속 구조에서 최상위 클래스는 Object다. 그래서 모든 클래스는 결국 Object의 특성을 물려받는다.
따라서 어떤 클래스의 인스턴스이든 상관없이 개발자는 toString( ) 메서드를 사용할 수 있다.
상속은 'is a' 관계를 만족해야 한다는 말이 있다.
그러나 이보다 더 명확한 표현이 있다.
1999년 Three Amigos라는 칭함을 받는 그래드 부치, 이바 야콥슨, 제임슨 럼버는 UML 사용자 지침서를 통해 'is kind of' 관계를 말하였다.
MS 개발자 사이트인 MSDN에서도 상속을 표현하는 'is a' 를 더 명확히 표현하면 'a kind of' 라고 명시하고 있다.
"하위 클래스 is kind of 상위 클래스 "
자바는 다중 상속을 지원하지 않는다.
그림과 같은 관계를 생각해보자. 늑대인간에게 달리라고 한다면 사람처럼 두 발로 달릴까? 아니면 늑대처럼 네 발로 달릴까?(이와 같은 문제를 다중 상속의 다이아몬드 문제라고 한다.)
다중 상속은 득보다 실이 더 많았기에 자바는 다중 상속을 포기하고 인터페이스를 도입하여 다중 상속의 득은 취하고 실은 과감히 버렸다.
🔹 상속 관계 : 하위 클래스 is a kind of 상위 클래스
🔹 해석 : 하위 클래스는 상위 클래스의 한 분류다.
🔹 예제 : 판다는 동물의 한 분류다.
🔹 인터페이스 : 구현 클래스 is able to 인터페이스
🔹 해석 : 구현 클래스는 인터페이스 할 수 있다.
🔹 예제 : 판다는 나무를 탈 수 있다.
인터페이스는 be able to, 즉 "무엇을 할 수 있는"이라는 표현 형태로 만드는 것이 좋다.
자바 API에서도 이러한 'be able to' 형식의 인터페이스를 많이 볼 수 있다.
🔹 Serializable 인터페이스 : 직렬화할 수 있는
🔹 Clonealbe 인터페이스 : 복제할 수 있는
🔹 Comparable 인터페이스 : 비교할 수 있는
🔹 Runnalbe 인터페이스 : 실행할 수 있는
상위 클래스는 하위 클래스에게 특성(속성과 메서드)을 상속해 주고,
인터페이스는 클래스가 '무엇을 할 수 있다'라고 하는 기능을 구현하도록 강제한다.
조금 깊이 생각해 보면
상위 클래스는 하위 클래스에게 물려줄 특성이 많을수록 좋고,
인터페이스는 구현을 강제할 메서드가 적을 수록 좋다.
클래스가 풍성할 수록 좋은 이유는 LSP(리스코프 치환 원칙)에 따른 이유라고 할 수 있고
인터페이스에 메서드가 적을 수록 좋은 이유는 ISP(인터페이스 분할 원칙)에 따른 이유라고 할 수 있다.
⬇️예시 코드
package 상속02
public class 동물 {
String className;
동물() {
className = "동물";
}
void show() {
System.out.println(className);
}
}
----------------------------------------------
package 상속02
public interface 헤엄칠수있는 {
void swim();
}
----------------------------------------------
package 상속02
public class 포유류 extends 동물 {
포유류() {
className = "포유류";
}
}
----------------------------------------------
package 상속02
public class 개 extends 포유류 implements 헤엄칠수있는 {
개() {
className = "개";
}
@Override
public void swim() {
System.out.println(className + " 수영중. 멍! 멍!");
}
}
🔹 객체 지향의 상속은 상위 클래스의 특성을 재사용하는 것이다.
🔹 객체 지향의 상속은 상위 클래스의 특성을 확장하는 것이다.
🔹 객체 지향의 상속은 is a kind of 관계를 만족해야 한다.
🔹 인터페이스는 be able to 표현 형태로 만드는 것이 좋다.
위에서는 언급하지 않았으나 하위 클래스의 인스턴스가 생성될 때, 힙 영역 메모리에는 상위 클래스의 인스턴스도 함께 생성됨
ex) public class 판다 extends 동물 {
// ...
}
판다 푸바오 = new 판다(); 실행 시에 힙 영역 메모리에 동물과 판다 함께 생성
김종민, '스프링 입문을 위한 자바 객체 지향의 원리와 이해', 위키북스 참고