Java 다형성 - 다운캐스팅 런타임 오류

다운 캐스팅을 잘못 참조한 경우 구동 중에 알 수 없는 이유로 종료된다. 이는 런타임 오류로 이용 중인 사용자에게도 큰 문제가 발생될 수 있으니 살펴보고 방지하도록 한다.


다운캐스팅 올바른 케이스

다운 캐스팅을 올바르게 사용하는 케이스

PolyMain.java

package poly;

public class PolyMain {
    public static void main(String[] args) {
        // IDE가 다운캐스팅을 권장하지 않는 이유

        // 문제없는 parent 변수
        Parent p1 = new Child();
        Child c1 = (Child) p1;
        c1.childMethod();
    }
}

실행결과

This is Child

메모리 상태

  • 처음 new Child() 크기의 인스턴스를 생성
  • 클래스 Parent, Child 크기의 메모리로 할당됨

부모가 자식으로 다운 캐스팅

  • (Child) p1 다운캐스팅으로 Child c1 변수로 할당

메소드 호출

  • c1.childMethod() 메소드를 실행함
  • Child 메모리 존재로 childMethod() 실행되어 반환함

다운 캐스팅 잘못된 케이스

다운 캐스팅을 잘못 사용된 케이스

package poly;

public class PolyMain {
    public static void main(String[] args) {
        // IDE가 다운캐스팅을 권장하지 않는 이유

        // 다운 캐스팅 parent 변수
        Parent p2 = new Parent();
        Child c2 = (Child) p2;
        c2.childMethod();
    }
}

실행 결과

Exception in thread "main" java.lang.ClassCastException: class poly.Parent cannot be cast to class poly.Child (poly.Parent and poly.Child are in unnamed module of loader 'app')
	at poly.PolyMain.main(PolyMain.java:9)

메모리 상태

  • 처음 new Parent() 크기의 인스턴스를 생성
  • 클래스 Parent 크기의 메모리로 할당됨

부모가 자식으로 다운 캐스팅

  • (Child) p1 다운캐스팅으로 Child c1 변수로 할당
  • 컴파일 오류로 IDE 에서 오류 위치를 알려줌

메소드 호출

  • 메소드 호출 시 참조할 수 없는 c1.childMethod() 메소드를 호출한 경우 런타임 오류가 발생함

예제 코드의 p2 다운캐스팅이 ClassCastException 런타임 오류로 심각한 문제를 야기하였다. 코드를 살펴 보도록 한다.

new Parent() 부모 타입으로 객체를 생성하고, 메모리에는 부모 타입만 있고, 자식 타입은 존재하지 않는다. 이러한 생성 결과를 변수 p2 타입으로 할당한다.
이러한 코드에서는 문제 없이 사용할 수 있다.

Parent p2 = new Parent()

p2 변수를 Child 강제로 다운캐스팅 시도 한 결과 오류가 발생한다. 메모리에는 (Child) 타입이 존재하지 않아 사용할 수 없다.
자바는 사용할 수 없는 타입으로 다운 캐스팅한 결과 ClassCastException 예외를 발생시키고, 다음 동작은 실행하지 않고 프로그램을 종료한다.
프로그램 종료로 인해 c1.childMethod() 코드는 실행하지 않는다.

Child c2 = (Child)p2

업캐스팅 안전한 이유

업캐스팅인 경우 앞서 말한 문제를 발생하지 않는다. 객체를 생성하면 타입의 부모타입과 함께 생성된다. 업스캐팅으로 부모 타입을 변경하는 경우 인스턴스에 모두 존재하기에 안전하다. 따라서 예제 코드의 (Parent) 업캐스팅을 생략해도 되는 이유이다.

  • Parent로 업캐스팅: Parent p = new Son()
  • Child로 업캐스팅: Child c = new Son()
  • Son로 본인 타입: Son s = new Son()

클래스 Parent, Child, Son 상속 관계
new Son() 인스턴스 생성 시 내부에는 Parent, Child, Son 모두 생성한다. 그래서 Son 타입은 Parent, Child, Son 타입으로 참조할 수 있다.상위로 올라가는 구조로 업캐스팅에서 문제가 발생되지 않는다.


다운 캐스팅이 위험한 이유

다운캐스팅인 경우 인스턴스에 존재하지 않는 하위 타입으로 캐스팅될 수 있다. 객체를 생성시 부모타입은 모두 생성되지만 자식 타입은 생성되지 않기에 개발자가 이러한 문제점을 인지하고 명시적으로 캐스팅을 해주어야 한다.

  • Parent로 업캐스팅: Parent p = new Child()
  • Child로 본인 타입: Child c = new Child()
  • Son로 다운캐스팅: Son s = new Child()
    • 하위 타입은 대입할 수없으므로 컴파일 오류

new Child() 인스턴스 생성 시 내부에 자신과 부모인 Parent, Child 가 생성한다. Child 타입 변수는 Parent, Child 으로 참조 할 수 있으며 상위로 올라가는 Parent 인스턴스가 내부에 있으므로 문제가 발생되지 않는다. 그렇지만 객체를 생성 시 Son() 하위 자식은 생성되지 않았으므로 하위 캐스팅으로 내려가는 다운캐스팅을 한 경우 인스턴스 내부에 없는 것을 참조를 시도하기에 문제가 발생하였다.


컴파일 오류, 런타임 오류

컴파일 오류

변수명과 잘못된 클래스 이름 등 자바를 실행하기 전 발생하는 오류이다. IDE 즉시 육안상으로 확인할 수 있는 안전하고 좋은 오류이다.

런타임 오류

프로그램이 실행 도중에 발생되는 문제로 안좋은 오류이다. 고객이 이용 중에 발생하기 때문이다.