1. 개요

이 짧은 예제에서, 우리는에 초점을 맞출 것이다 ClassCastException이 하는 일반적인 자바 예외 .

ClassCastException코드가 하위 유형이 아닌 유형에 대한 참조를 캐스팅하려고 시도했음을 알리는 확인되지 않은 예외 입니다 .

이 예외가 발생하는 몇 가지 시나리오와 이를 방지할 수 있는 방법을 살펴보겠습니다.

2. 명시적 캐스팅

다음 실험을 위해 다음 클래스를 고려해 보겠습니다.

public interface Animal {
    String getName();
}
public class Mammal implements Animal {
    @Override
    public String getName() {
        return "Mammal";
    }
}
public class Amphibian implements Animal {
    @Override
    public String getName() {
        return "Amphibian";
    }
}
public class Frog extends Amphibian {
    @Override
    public String getName() {
        return super.getName() + ": Frog";
    }
}

2.1. 캐스팅 수업

지금까지 ClassCastException발생하는 가장 일반적인 시나리오 는 명시적으로 호환되지 않는 유형으로 캐스팅하는 것입니다.

예를 들어 FrogMammal 로 캐스팅해 보겠습니다 .

Frog frog = new Frog();
Mammal mammal = (Mammal) frog;

여기서 ClassCastException을 예상할 수 있지만 실제로 "호환되지 않는 유형: Frog를 Mammal로 변환할 수 없습니다"라는 컴파일 오류가 발생합니다. 그러나 일반적인 수퍼 유형을 사용하면 상황이 바뀝니다.

Animal animal = new Frog();
Mammal mammal = (Mammal) animal;

이제 두 번째 줄에서 ClassCastException얻습니다 .

Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class Mammal (Frog and Mammal are in unnamed module of loader 'app') 
at Main.main(Main.java:9)

FrogMammal 의 하위 유형이 아니기 때문에 Mammal 에 대한 확인된 다운캐스트 Frog 참조와 호환되지 않습니다 . 이 경우 Animal 변수가 호환 가능한 유형의 참조를 보유 할 수 있으므로 컴파일러는 우리를 도울 수 없습니다 .

분명히 호환되지 않는 클래스로 캐스트를 시도할 때만 컴파일 오류가 발생한다는 점은 흥미롭습니다. Java는 다중 인터페이스 상속을 지원하지만 클래스에 대해서는 단일 상속만 지원하기 때문에 인터페이스의 경우에도 마찬가지입니다. 따라서 컴파일러는 참조 유형이 특정 인터페이스를 구현하는지 여부를 결정할 수 없습니다. 예를 들어 보겠습니다.

Animal animal = new Frog();
Serializable serial = (Serializable) animal;

우리는 얻을 ClassCastException이 대신 컴파일 오류의 두 번째 줄에를 :

Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class java.io.Serializable (Frog is in unnamed module of loader 'app'; java.io.Serializable is in module java.base of loader 'bootstrap') 
at Main.main(Main.java:11)

2.2. 배열 캐스팅

클래스가 캐스팅을 처리하는 방법을 살펴보았으므로 이제 배열을 살펴보겠습니다. 배열 캐스팅은 클래스 캐스팅과 동일하게 작동합니다. 그러나 우리는 자동 박싱 및 유형 승격 또는 그 부족으로 인해 혼란스러워 할 수 있습니다.

따라서 다음 캐스트를 시도할 때 기본 배열에 대해 어떤 일이 발생하는지 봅시다.

Object primitives = new int[1];
Integer[] integers = (Integer[]) primitives;

두 번째 줄 은 자동 박싱 이 배열에 대해 작동하지 않기 때문에 ClassCastException 을 발생 시킵니다.

유형 프로모션은 어떻습니까? 다음을 시도해 보겠습니다.

Object primitives = new int[1];
long[] longs = (long[]) primitives;

또한 유형 승격 이 전체 배열에 대해 작동하지 않기 때문에 ClassCastException이 발생 합니다.

2.3. 안전한 캐스팅

명시적 캐스팅의 경우 instanceof를 사용하여 캐스팅시도하기 전에 유형의 호환성을 확인하는 것이 좋습니다 .

안전한 캐스트 예를 살펴보겠습니다.

Mammal mammal;
if (animal instanceof Mammal) {
    mammal = (Mammal) animal;
} else {
    // handle exceptional case
}

3. 힙 오염

당으로 자바 사양 : " 힙 오염 프로그램이 컴파일시 체크되지 않은 경고를 야기 할 것 원시 유형을 포함하는 몇 가지 작업을 수행 한 경우에만 발생할 수 있습니다."

실험을 위해 다음과 같은 일반 클래스를 고려해 보겠습니다.

public static class Box<T> {
    private T content;

    public T getContent() {
        return content;
    }

    public void setContent(T content) {
        this.content = content;
    }
}

이제 다음과 같이 힙을 오염시키려고 합니다.

Box<Long> originalBox = new Box<>();
Box raw = originalBox;
raw.setContent(2.5);
Box<Long> bound = (Box<Long>) raw;
Long content = bound.getContent();

마지막 줄은 Long 에 대한 D 이중 참조를 변환할 수 없으므로 ClassCastExceptionthrow합니다 .

4. 제네릭 유형

Java에서 제네릭을 사용할 때 유형 삭제에 주의해야 합니다 . 이는 일부 조건에서도 ClassCastException 으로 이어질 수 있습니다 .

다음과 같은 일반적인 방법을 고려해 보겠습니다.

public static <T> T convertInstanceOfObject(Object o) {
    try {
        return (T) o;
    } catch (ClassCastException e) {
        return null;
    }
}

이제 다음과 같이 부르겠습니다.

String shouldBeNull = convertInstanceOfObject(123);

언뜻 보면 catch 블록에서 반환된 null 참조를 합리적으로 기대할 수 있습니다. 그러나 런타임 시 유형 삭제로 인해 매개변수가 String 대신 Object 로 캐스트됩니다 . 따라서 컴파일러는 ClassCastException을 발생 시키는 String 에 Integer할당하는 작업에 직면해 있습니다.

5. 결론

이 기사에서는 부적절한 캐스팅에 대한 일련의 일반적인 시나리오를 살펴보았습니다.

암시적이든 명시적이든 , 대상 유형이 동일하거나 실제 유형의 자손이 아닌 한 다른 유형에 대한 Java 참조를 캐스팅하면 ClassCastException이 발생할 수 있습니다 .

이 기사에 사용된 코드 는 GitHub 에서 찾을 수 있습니다  .

Junit footer banner