다형성의 의미와 무늬 vs 실체 (008-002~004)
전제 클래스 (아래 문제 공통)
public class Animal {
public void shout() {
System.out.println("...");
}
}
public class Dog extends Animal {
@Override
public void shout() {
System.out.println("Woof");
}
}
public class Cat extends Animal {
@Override
public void shout() {
System.out.println("Meow");
}
}다음 코드의 출력은?
Animal[] animals = new Animal[] { new Dog(), new Animal(), new Cat() };
for (Animal animal : animals) {
animal.shout();
}Woof
...
Meow
- 원칙: 실행되는 구현은 변수의 자료형(무늬)이 아니라 실행 중 실제 개체의 자료형(실체) 기준
new Animal()은 실체가 부모형이므로 부모 구현이 실행됨- 부모형 배열에 다른 종류의 개체를 담아 같은 지시를 내리는 것 — 상속 덕분에 가능
다음 코드의 출력은? (캐스팅 함정)
Dog dog = new Dog();
Animal animal = (Animal) dog;
animal.shout();Woof
- 부모형으로 캐스팅해도 바뀌는 것은 무늬뿐 — 실체는 여전히
Dog라 자식 구현이 실행됨 - 본질: 캐스팅은 개체를 바꾸는 게 아니라 컴파일러가 보는 타입을 바꾸는 것 (컴파일러와의 협업) — 업캐스트는 무늬를 넓혀 검사 없이 통과, 다운캐스트는 무늬를 좁히므로 실체가 맞는지 런타임 검사(
ClassCastException)로 미뤄짐- 컴파일러의 판정 기준은 가능성: 실행 중 호환 가능성이 0%면(형제 관계 등) 컴파일 오류로 차단, 가능성이 있으면 통과시키고 실체 대조는 런타임에 위임 — 상속과 명시적 캐스팅 참고
- 오버라이딩된 부모의 원래 구현을 호출하는 방법은 두 가지뿐
- 자식 클래스 안에서
super.shout()호출 - 실행 중 개체 자체가 부모형인 경우 —
new Animal().shout()
- 자식 클래스 안에서
animal.shout() 호출에서 컴파일 중에 결정되는 것과 실행 중에 결정되는 것은?
- 컴파일 중: 함수 시그니처 — 변수 자료형(
Animal)에shout()시그니처가 존재하는지 (없으면 컴파일 오류) - 실행 중: 실제로 어떤 함수 구현이 실행되는지 — 실체의 자료형 기준 (late binding)
다형성을 구현하려면 왜 상속이 필요한가? (5문장 이내)
다형성은 다른 종류의 개체에 같은 지시를 내리면 동작을 달리하는 것임. “같은 지시”는 동일한 함수 시그니처 호출을 뜻하므로 모든 종류가 공유하는 시그니처가 필요하고, 그 시그니처가 놓이는 곳이 부모 클래스임(무늬). “다른 종류의 개체”에 지시하려면 부모형 변수 하나가 여러 자식 실체를 참조할 수 있어야 하는데, 이 대입을 허용하는 관계가 상속(is-a)임. “동작을 달리”는 자식이 시그니처를 유지한 채 바디만 덮어쓰는 오버라이딩으로 구현되고, 실행 중 실체 기준으로 선택됨(late binding). 결국 정의의 세 요소가 각각 상속을 전제로 함.
- 컴파일러 관점 보강: 호출부(
animal.shout())가 컴파일되려면 컴파일러가 무늬(부모형)에서 시그니처의 존재를 확인할 수 있어야 함 — 상속이 그 보장을 제공 - 전개 순서 기억법: 정의 3요소(“같은 지시” → 공유 시그니처 = 부모 / “다른 종류” → 부모형 변수에 자식 대입 = is-a / “다른 동작” → 오버라이딩 + late binding) 각각을 상속 요구로 번역
다형성이 없는 절차적 언어에서는 “종류마다 다른 동작”을 어떻게 처리하며, 무엇이 불편한가?
if 문(타입 분기)으로 처리
- 종류를 판단하는 중재자 코드가 필요하고, 새로운 종류가 추가될 때마다 분기문을 고쳐야 해서 불편함
강의에서 말하는 OOP의 다형성은 정확히 무엇인가?
서브타입 다형성(subtype polymorphism)
- “다른 종류의 개체”(타입이 다름)에 “같은 지시”(동일한 함수 시그니처 호출)를 내리면 “동작을 달리”(실행되는 구현이 다름) 하는 것