[java] 수퍼 클래스에서 서브 클래스로 명시 적 캐스팅

public class Animal {
    public void eat() {}
}

public class Dog extends Animal {
    public void eat() {}

    public void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = (Dog) animal;
    }
}

할당 Dog dog = (Dog) animal;은 컴파일 오류를 생성하지 않지만 런타임에을 생성합니다 ClassCastException. 컴파일러에서이 오류를 감지 할 수없는 이유는 무엇입니까?



답변

캐스트를 사용하면 본질적으로 컴파일러에게 “믿습니다. 저는 전문가입니다. 저는 제가하고있는 일을 알고 있으며 보장 할 수는 없지만이 animal변수는 개가 될 것입니다. “

동물이 실제로 개가 아니기 때문에 (동물, 할 수 Animal animal = new Dog();있고 개가 될 수 있음) VM은 신뢰를 위반했기 때문에 런타임에 예외를 throw합니다 (컴파일러는 모든 것이 정상이며 아니!)

컴파일러는 모든 것을 맹목적으로 받아들이는 것보다 조금 더 똑똑합니다. 다른 상속 계층 구조에서 객체를 캐스트하려고하면 (예를 들어 Dog를 String으로 캐스트) 컴파일러는 작동하지 않을 수 있다는 것을 알고 있기 때문에 다시 던져 버립니다.

본질적으로 컴파일러가 불평하는 것을 막기 때문에 캐스팅 할 때마다 if 문 (또는 그 효과에 영향을 미침) ClassCastException을 사용하여 a 를 유발하지 않는지 확인하는 것이 중요합니다 instanceof.


답변

이론적 으로 개가 될 Animal animal 있기 때문에 :

Animal animal = new Dog();

일반적으로 다운 캐스팅은 좋은 생각이 아닙니다. 피해야합니다. 당신이 그것을 사용하는 경우, 당신은 확인을 포함하는 것이 좋습니다 :

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}


답변

이러한 종류의 ClassCastException을 피하려면 다음이있는 경우 :

class A
class B extends A

A의 객체를 취하는 B에서 생성자를 정의 할 수 있습니다. 이런 식으로 “캐스트”를 수행 할 수 있습니다.

public B(A a) {
    super(a.arg1, a.arg2); //arg1 and arg2 must be, at least, protected in class A
    // If B class has more attributes, then you would initilize them here
}


답변

Michael Berry의 답변을 정교화합니다.

Dog d = (Dog)Animal; //Compiles but fails at runtime

여기서는 컴파일러에게 “신뢰하십시오. d실제로 Dog객체를 참조하고 있다는 것을 알고 있습니다”라고 말하는 것은 아닙니다.
다운 캐스트를 수행 할 때 컴파일러가 우리를 신뢰해야한다는 것을 기억하십시오 .

컴파일러는 선언 된 참조 유형에 대해서만 알고 있습니다. 런타임시 JVM은 오브젝트가 실제로 무엇인지 알고 있습니다.

따라서 런타임시 JVM Dog d이 실제로 객체가 Animal아닌 Dog객체를 참조 하고 있음을 알아 냈습니다 . 이봐 … 당신은 컴파일러에 거짓말을하고 큰 지방을 던졌습니다 ClassCastException.

따라서 다운 캐스팅하는 경우 instanceof테스트를 사용 하여 실수를 피해야합니다.

if (animal instanceof Dog) {
Dog dog = (Dog) animal;
}

이제 질문이 떠 오릅니다. 지옥 컴파일러가 다운 캐스트를 허용하는 이유는 무엇 java.lang.ClassCastException입니까?

대답은 컴파일러가 할 수있는 모든 것이 두 유형이 동일한 상속 트리에 있는지 확인하는 것입니다. 따라서 다운 캐스트 전에 올 수있는 코드에 따라 animal유형이 가능합니다 dog.

컴파일러는 런타임에 작동 할 수있는 것을 허용해야합니다.

다음 코드 스 니펫을 고려하십시오.

public static void main(String[] args)
{
    Dog d = getMeAnAnimal();// ERROR: Type mismatch: cannot convert Animal to Dog
    Dog d = (Dog)getMeAnAnimal(); // Downcast works fine. No ClassCastException :)
    d.eat();

}

private static Animal getMeAnAnimal()
{
    Animal animal = new Dog();
    return animal;
}

그러나 컴파일러가 캐스트가 가능하지 않다고 확신하면 컴파일이 실패합니다. IE 상속 계층이 다른 객체를 캐스팅하려고하면

String s = (String)d; // ERROR : cannot cast for Dog to String

다운 캐스팅과 달리 업 캐스팅은 암시 적으로 작동합니다. 업 캐스팅 할 때 다운 캐스팅과 반대로 호출 할 수있는 메서드 수를 암시 적으로 제한하므로 나중에보다 구체적인 메서드를 호출 할 수 있습니다.

Dog d = new Dog();
Animal animal1 = d; // Works fine with no explicit cast
Animal animal2 = (Animal) d; // Works fine with n explicit cast

동물이 할 수 있다고 주장하는 개 IS-A 동물, 개가 할 수 있다고 말하면 위의 두 가지 모두 예외없이 잘 작동합니다. 그러나 그것은 사실이 아닙니다.


답변

인스턴스 유형 이 Animal 이기 때문에 코드에서 컴파일 오류가 발생합니다 .

Animal animal=new Animal();

여러 가지 이유로 Java에서는 다운 캐스팅이 허용되지 않습니다. 자세한 내용은 여기 를 참조 하십시오 .


답변

설명했듯이 불가능합니다. 서브 클래스의 메소드를 사용하려면 수퍼 클래스에 메소드를 추가 할 가능성 (비어있을 수 있음)을 평가하고 다형성 덕분에 원하는 서브 클래스를 가져 오는 서브 클래스에서 호출하십시오. 따라서 d.method ()를 호출하면 호출이 캐스팅과 함께 성공하지만 개체가 개가 아닌 경우 문제가 없습니다.


답변

@Caumons의 답변을 개발하려면 :

한 아버지 클래스에 많은 자녀가 있고 그 클래스에 공통 필드를 추가해야한다고 상상해보십시오. 언급 된 접근 방식을 고려하면 각 하위 클래스로 하나씩 이동하여 새 필드의 생성자를 리팩터링해야합니다. 따라서이 시나리오에서 솔루션은 유망한 솔루션이 아닙니다.

이제이 솔루션을 살펴보십시오.

아버지는 각 자녀로부터 자기 물건을받을 수 있습니다. 아버지 클래스는 다음과 같습니다.

public class Father {

    protected String fatherField;

    public Father(Father a){
        fatherField = a.fatherField;
    }

    //Second constructor
    public Father(String fatherField){
        this.fatherField = fatherField;
    }

    //.... Other constructors + Getters and Setters for the Fields
}

다음은 아버지 생성자 중 하나를 구현 해야하는 자식 클래스입니다.이 경우 위에서 언급 한 생성자입니다.

public class Child extends Father {

    protected String childField;

    public Child(Father father, String childField ) {
        super(father);
        this.childField = childField;
    }

    //.... Other constructors + Getters and Setters for the Fields

    @Override
    public String toString() {
        return String.format("Father Field is: %s\nChild Field is: %s", fatherField, childField);
    }
}

이제 응용 프로그램을 테스트합니다.

public class Test {
    public static void main(String[] args) {
        Father fatherObj = new Father("Father String");
        Child child = new Child(fatherObj, "Child String");
        System.out.println(child);
    }
}

결과는 다음과 같습니다.

아버지 분야 : 아버지 문자열

자식 필드 : 자식 문자열

이제 자녀 코드가 깨질 염려없이 아버지 클래스에 새로운 필드를 쉽게 추가 할 수 있습니다.