다형성은 이름 그대로 "다양한 형태", "여러 형태"를 의미한다. 프로그래밍에서 다형성은 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 뜻한다. 보통 하나의 객체는 하나의 타입으로 고정되어 있다. 하지만 다형성을 사용하면 하나의 객체가 다른 타입으로 사용될 수 있다.
부모는 자식을 품을 수 있다. 만약 손자가 존재한다면, 손자도 참조할 수 있다. 자바에서 부모타입은 자신은 물론이고, 자신을 기준으로 모든 자식 타입을 참조할 수 있다. 이것이 바로 다양한 형태를 참조할 수 있다고 해서 다형적 참조라고 한다.
public class Parent {
public void parentMethod() {
System.out.println("Parent.parentMethod");
}
}
public class Child extends Parent {
public void childMethod() {
System.out.println("Child.childMethod");
}
}
public class PolyMain {
public static void main(String[] args) {
// 부모 변수가 부모 인스턴스 참조
System.out.println("Parent -> Parent");
Parent parent = new Parent();
parent.parentMethod();
// 자식 변수가 자식 인스턴스 참조
System.out.println("Child -> Child");
Child child = new Child();
child.parentMethod();
child.childMethod();
// 부모 변수가 자식 인스턴스 참조(다형적 참조)
Parent poly = new Child();
poly.parentMethod();
// Child child = new Parent(); 자식은 부모를 담을 수 없다.
// 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
poly.childMethod();
}
}
Parent poly에서 parentMethod()를 호출하게 된다면 어떻게 될까, 먼저 poly.parentMethod()를 호출하면 먼저 참조값을 사용해서 인스턴스를 찾게 된다. 그리고 다음으로 인스턴스 안에서 실행할 타입도 찾아야 한다. poly는 Parent 타입이다. 그렇기 때문에 Parent클래스부터 시작해서 필요한 기능을 찾는다. 인스턴스의 Parent클래스에 parentMethod()가 있다. 따라서 해당 메서드가 호출된다.

그럼 만약 poly에서 childMethod()를 호출하게 되면 어떻게 될까, 먼저 참조값을 통해 인스턴스를 찾고, 그 다음으로 인스턴스 안에서 실행할 타입을 찾아야 한다.
그럼 poly는 Parent 타입이니깐 Parent 클래스부터 시작해서 필요한 기능을 찾는다. 그런데 상속 관계는 부모 방향으로 찾아 올라갈 순 있지만 자식 방향으로 찾아 내려갈 순 없다. Parent는 부모 타입이고 상위에 부모가 없기 때문에 childMethod()를 찾을 수 없으므로 컴파일 오류가 발생한다.
그럼 만약 childMethod()를 호출하고 싶으면 어떻게 해야할까유 -> 캐스팅
부모는 자식을 담을 수 있지만, 자식은 부모를 담을 수 없다. 부모 타입을 사용하는 변수를 자식 타입에 대입하려고 하면 컴파일 오류가 발생한다.
이때 다운 캐스팅라는 기능을 사용해서 부모 타입을 잠깐 자식 타입으로 변경하면 된다.
Child child = (Child) poly
(타입)처럼 괄호와 그 사이에 타입을 지정하면 참조 대상을 특정 타입으로 변경할 수 있다. 이렇게 특정 타입으로 변경하는 것을 캐스팅이라고 한다.
Child child = (Child) poly // 다운 캐스팅을 통해 부모 타입을 자식 타입으로 변환한 다음에 대입 시도
Child child = (Child) x001 // 참조값을 읽은 다음 자식 타입으로 지정
Child child = x001 // 최종 결과
하지만 이렇게 캐스팅을 한다고 해서 Parent poly의 타입이 변하는 것은 아니다. 해당 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 되는 것이다. 따라서 poly의 타입은 그대로 Parent이다.
Child child = (Child) poly
child.childMethod()
하지만 위 방식처럼 다운 캐스팅 결과를 변수에 담아두는 과정이 번거로우니깐 일시적으로 다운캐스팅 하는 방식을 사용하면 된다.
public class CastingMain2 {
public static void main(String[] args) {
Parent poly = new Child();
// 일시적 다운캐스팅 - 해당 메서드를 호출하는 순간만 다운캐스팅
((Child) poly).childMethod();
}
}
public class CastingMain4 {
public static void main(String[] args) {
Parent parent1 = new Child();
Child child1 = (Child) parent1;
child1.childMethod();
Parent parent2 = new Parent();
Child child2 = (Child) parent2; // 런타임 오류 - ClassCastException
child2.childMethod(); // 실행 불가
}
}
parent2에는 자식이 없으니깐 당연히 자식꺼 메서드를 실행할 수 없겠지요
다운캐스팅과 반대로 현재 타입을 부모 타입으로 변경하는 것을 업캐스팅이라고 한다.
public class CastingMain3 {
public static void main(String[] args) {
Child child = new Child();
Parent parent1 = (Parent) child; // 업캐스팅은 생략 가능, 생략 권장
Parent parent2 = child; // 업캐스팅 생략
parent1.parentMethod();
parent2.parentMethod();
}
}
다형성에서 참조형 변수는 이름 그대로 다양한 자식을 대상으로 참조할 수 있다. 그런데 참조하는 대상이 다양하기 때문에 어떤 인스턴스를 참조하고 있는지 확인하려면 어떻게 해야할까?
Parent parent1 = new Parent();
Parent parent2 = new Child();
여기서 Parent는 자신과 같은 Parent의 인스턴스도 참조할 수 있고, 자식 타입인 Child의 인스턴스도 참조할 수 있다. 이때 parent1, parent2 변수가 참조하는 인스턴스의 타입을 확인하고 싶을 때 instanceof키워드를 사용한다.

Parent, Child 모두 value라는 같은 멤버 변수를 가지고 있다고 가정하자. 여기서 멤버변수는 오버라이딩 되지 않는다.
public class Parent {
public String value ="parent";
public void method() {
System.out.println("Parent.method");
}
}
-> Child에서 Parent의 method 오버라이딩
public class Child extends Parent {
public String value = "child";
@Override
public void method() {
System.out.println("Child.method");
}
}
public class OverridingMain {
public static void main(String[] args) {
// 자식 변수가 자식 인스턴스 참조
Child child = new Child();
System.out.println("Child -> Child");
System.out.println("value= " + child.value);
child.method();
// 부모 변수가 부모 인스턴스 참조
Parent parent = new Parent();
System.out.println("Parent -> Parent");
System.out.println("value= " + parent.value);
parent.method();
Parent poly = new Child();
System.out.println("Parent -> Child");
System.out.println("value= " + poly.value); // parent가 출력된다. 왜냐면? 변수는 오버라이딩 안돼요
poly.method();
}
}