다형성(Polymorphism)
하나의 참조 타입으로 여러 구현체를 다룰 수 있는 성질. 업캐스팅 + 동적 바인딩의 조합.
유지보수성과 확장성의 핵심. 신규 자식 클래스가 추가돼도 기존 코드는 수정하지 않아도 되는 OCP(개방-폐쇄 원칙)를 가능하게 한다. 실기 코드 추적의 최빈출 주제.
다형성은 세 가지 메커니즘이 결합된 결과다. ① — 자식 객체를 부모 타입 변수에 담는 단계. ② — 자식이 부모 메서드를 재정의하는 단계. ③ — 실행 시점에 실제 객체의 메서드가 호출되는 단계. 이 셋이 모두 성립해야만 진짜 다형성이다.
예를 들어 도형 계층을 생각해 보자. Shape를 부모 추상 클래스로 두고 Circle·Square·Triangle이 상속받아 각자의 방식으로 `area()`를 구현한다.
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
double area() { return 3.14 * r * r; }
}
class Square extends Shape {
double s;
Square(double s) { this.s = s; }
double area() { return s * s; }
}
// ★ 여기가 다형성의 힘
Shape[] shapes = { new Circle(2), new Square(3) };
double total = 0;
for (Shape sh : shapes) total += sh.area();
System.out.println(total); // 21.56Circle과 Square를 동일한 Shape 배열에 섞어 담을 수 있고, 반복문은 어떤 자식이든 신경 쓰지 않고 area()만 호출한다. 나중에 Triangle을 추가해도 이 반복문은 한 줄도 수정할 필요 없다. 이것이 의 실체다.
핵심은 세 번째 단계인 이다. 아래 인터랙션에서 참조 변수에 담는 객체를 바꿔 보자. 같은 호출 `ref.sound()`이 실행 시점의 실제 객체에 따라 어떻게 다른 메서드로 이어지는지 직접 확인할 수 있다.
참조 타입은 항상 Animal이지만, 실행되는 sound()는 **실제 객체의 클래스**에 따라 달라진다.
업캐스팅(자식→부모)은 자동. 다운캐스팅(부모→자식)은 명시적 + `instanceof` 검사가 안전하다.
Shape s = new Circle(5); // 업캐스팅 (자동)
if (s instanceof Circle) {
Circle c = (Circle) s; // 다운캐스팅 (명시적)
System.out.println(c.r);
}주의 함정: 필드는 (선언 타입 따라), 메서드는 (실제 객체 따라). 이 차이가 실기 최상급 함정이다.
동적 바인딩 더 깊이 파기
참조 타입과 실제 객체가 다를 때, JVM이 어떻게 실행할 메서드를 찾는지 단계별로 체험해 보자.
다음 코드의 출력은?
class A {
int x = 10;
int getX() { return x; }
}
class B extends A {
int x = 20;
int getX() { return x; }
}
public class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a.x + "," + a.getX());
}
}하나의 참조 변수가 여러 타입의 객체를 참조하고, 호출하는 메서드의 실체가 실행 시점에 결정되는 객체지향의 성질은?