객체지향의 특징은 캡슐화, 상속, 다형성이 있다. 이 중에서 “다형성”에 대해 배워보자.
다형성은 객체지향의 꽃이라고 불린다.
package poly.basic;
public class Parent {
public void parentMethod() {
System.out.println("Parent.parentMethod");
}
}
package poly.basic;
public class Child extends Parent {
public void childMethod() {
System.out.println("Child.childMethod");
}
}
package poly.basic;
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();
//부모 변수가 자식 인스턴스 참조(다형적 참조)
System.out.println("Parent -> Child");
Parent poly = new Child();
poly.parentMethod();
//Child child1 = new Parent(); //자식은 부모를 담을 수 없다.
//자식의 기능은 호출할 수 없다. 컴파일 오류 발생
//poly.childMethod();
}
}
package poly.basic;
public class CastingMain1 {
public static void main(String[] args) {
//부모 변수가 자식 인스턴스 참조(다형적 참조)
Parent poly = new Child(); //x001
//단 자식의 기능은 호출할 수 없다. 컴파일 오류 발생
//poly.childMethod();
//다운캐스팅(부모 타입 -> 자식 타입)
Child child = (Child) poly; //x001이라는 참조값만 복사하여 사용
child.childMethod();
}
}
Parent parent = new Child();
((Child) parent).childMethod();
(Child) parent.childMethod()
가 되고, Java가 다운 캐스팅보다 메서드 호출을 높은 우선순위로 인식하여 컴파일 에러가 발생한다.Child child = new Child();
Parent parent = (Parent) child; // 업캐스팅은 생략 권장
부모 인스턴스로 만들게되면 힙 영역에서 자식 인스턴스는 생성되지 않음을 주의해야 한다.
업캐스팅 vs 다운캐스팅
위 경우처럼 처음에 new를 통해 자식 인스턴스를 만들게되면
힙 영역에서 부모 인스턴스가 모두 만들어지기에 업캐스팅하여도 아무 문제 없다.
하지만 위처럼 클래스 C에겐 부모 입장인 클래스 B로 인스턴스를 생성 했다면,
힙 영역에서 자식인 C 인스턴스는 생성되지 않는다.
이렇게 부모 인스턴스에서 다운 캐스팅을 하고 자식 메서드를 호출하면 “런타임 오류”가 발생한다.
// 위 그림 참조
C c = (C) new B(); //강제 다운 캐스팅
c.methodC(); //런타임 오류, ClassCastException 발생
// 또는
B b = new B();
((C) b).methodC(); //런타임 오류, ClassCastException 발생
다운 캐스팅은 개발자가 강제로 할 수 있기에, 이 점을 주의해야 한다.
instanceof
를 사용하여 상속관계를 가지고 있는 변수의 인스턴스 타입을 확인할 수 있다.package poly.basic;
public class CastingMain5 {
public static void main(String[] args) {
Parent parent1 = new Parent();
System.out.println("parent1 호출");
call(parent1);
Parent parent2 = new Child();
System.out.println("parent2 호출");
call(parent2);
}
private static void call(Parent parent) {
parent.parentMethod();
if (parent instanceof Child) { // 변수의 인스턴스 타입 확인
System.out.println("Child 인스턴스 맞음");
Child child = (Child) parent; // 다운 캐스팅해도 안전함
child.childMethod();
}
}
}
/* 실행 결과
parent1 호출
Parent.parentMethod
parent2 호출
Parent.parentMethod
Child 인스턴스 맞음
Child.childMethod
*/
"확인할 변수" instancof "확인할 타입"
으로 작성하면 되며, 결과는 Boolean
이다.Parent parent1 = new Parent();
Parent parent2 = new Child();
// 1. 부모인지 검사
parent1 instanceof Parent //true
-> Parent p = parent1
parent2 instanceof Parent //true *
-> Parent p = parent2
// 2. 자식인지 검사
parent1 instanceof Child //false
-> Child c = parent1
parent2 instanceof Child //true
-> Child c = parent2
parent2
는 Child
인데 true
가 나왔다.parent2
가 참조하는 Child
인스턴스에는 Parent
인스턴스도 생성되어있기 때문이다.Parent p = parent2
가 가능한지 아닌지를 판단하면 된다.true
가 나오는 것이다.package poly.basic;
public class CastingMain6 {
public static void main(String[] args) {
Parent parent1 = new Parent();
System.out.println("parent1 호출");
call(parent1);
Parent parent2 = new Child();
System.out.println("parent2 호출");
call(parent2);
}
private static void call(Parent parent) {
parent.parentMethod();
//Child 인스턴스인 경우 childMethod() 실행
if (parent instanceof Child child) { // Java 16부터 가능
System.out.println("Child 인스턴스 맞음");
//Child child = (Child) parent; //다운 캐스팅으로 생략 가능
child.childMethod();
}
}
}
"확인할 변수" instancof "확인할 타입" "다운 캐스팅할 변수명"
으로 바로 변수를 선언할 수 있다.instanceof
로 변수의 인스턴스가 확인이 되어 런타임 오류가 발생하지 않는 경우에만package poly.overriding;
public class Parent {
public String value = "parent";
public void method() {
System.out.println("Parent.method");
}
}
package poly.overriding;
public class Child extends Parent {
public String value = "child";
@Override
public void method() {
System.out.println("Child.method");
}
}
package poly.overriding;
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); //변수는 오버라이딩X
poly.method(); //메서드 오버라이딩!
}
}
/* 실행 결과
Child -> Child
value = child
Child.method
Parent -> Parent
value = parent
Parent.method
Parent -> Child
value = parent
Child.method // 자식 메서드가 실행됨
*/
기존에 배웠던 상속관계에서 메서드 호출 시, “호출자”의 타입에 대해 먼저 찾아보고, 없으면 부모 클래스에 있는지 확인하는 것이었다.
위 코드에서, 부모와 자식 클래스 모두 method() 라는 메서드를 가지고 있다.
이때, 다형적 참조를 통해 부모 변수가 자식 인스턴스를 담고 있다.
오버라이딩된 메서드가 존재한다면, 절대적으로 우선권을 가지기 때문이다.
💡 다형성의 핵심은 2가지다. 이제 다음 Section에서 아래 2가지를 실습해보자.
1. 다형적 참조
2. 다형성의 메서드 오버라이딩