상속 관계에 있을 때, 조상 클래스 타입의 참조 변수로 자식 클래스의 객체를 참조할 수 있는 성질을 말합니다. 실제 객체는 자식이지만, 겉모습(타입)은 부모의 탈을 쓰고 있는 것이죠.
Parent p = new Child(); 코드가 실행되면 어떤 일이 벌어질까요?
Parent 타입의 참조 변수 p가 생성됩니다.Child 인스턴스가 생성됩니다. 이때 메모리에는 자식 클래스의 멤버들이 모두 올라갑니다.p는 힙에 생성된 Child 객체의 주소를 가리킵니다.major 등)가 존재하더라도, 참조 변수 p의 타입이 Parent라면 Parent에 정의된 멤버에만 접근할 수 있습니다.다형성에서 가장 헷갈리는 부분이 필드와 메서드의 동작 차이입니다.
Parent p = new Child();
System.out.println(p.x); // 부모의 필드 출력
System.out.println(p.method()); // 자식의 오버라이딩된 메서드 실행 (동적 바인딩)
참고:
static메서드는 동적 바인딩이 일어나지 않습니다. 클래스 레벨에서 관리되므로 참조 변수 타입의 영향을 받습니다. 따라서클래스명.메서드()형태로 호출하는 것이 권장됩니다.
객체를 참조하는 타입을 바꿀 때 주의해야 할 규칙이 있습니다.
잘못된 다운캐스팅은 ClassCastException을 발생시킵니다. 이를 방지하기 위해 instanceof로 타입을 확인해야 합니다.
if-else 문에서 사용할 때 자식 타입(좁은 범위)부터 체크해야 합니다. 부모 타입을 먼저 체크하면 자식 객체도 부모 타입으로 판정되어 로직이 꼬일 수 있습니다.// 기존 방식
if (person instanceof Student) {
Student st = (Student) person;
st.study();
}
// Java 16+ 방식 (Smart Casting)
if (person instanceof Student st) {
st.study();
}
모든 요리사(Chef)는 요리(cook)를 하지만, '그냥 요리사'가 하는 '일반적인 요리'는 정의하기 모호합니다. 이럴 때 구현부 없이 선언부만 있는 추상 메서드를 포함한 추상 클래스를 사용합니다.
new Chef()와 같이 인스턴스를 만들 수 없습니다.public abstract class Chef {
String name;
public void eat() { System.out.println("냠냠"); }
public abstract void cook(); // 자식에서 반드시 구현해야 함!
}
자식 클래스의 생성자에서 부모의 필드를 초기화할 때, this.name = name; 보다는 super(name, age);를 호출하는 것이 좋습니다.
private으로 변경되어도 자식 코드를 수정할 필요가 없어 캡슐화에 유리합니다.자바는 클래스의 단일 상속만 허용합니다. 만약 두 부모 클래스에 동일한 이름의 메서드가 있다면, 자식 입장에서 어떤 메서드를 상속받아야 할지 결정할 수 없는 "다이아몬드 문제"가 발생하기 때문입니다.