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();
답변
설명했듯이 불가능합니다. 서브 클래스의 메소드를 사용하려면 수퍼 클래스에 메소드를 추가 할 가능성 (비어있을 수 있음)을 평가하고 다형성 덕분에 원하는 서브 클래스를 가져 오는 서브 클래스에서 호출하십시오. 따라서 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);
}
}
결과는 다음과 같습니다.
아버지 분야 : 아버지 문자열
자식 필드 : 자식 문자열
이제 자녀 코드가 깨질 염려없이 아버지 클래스에 새로운 필드를 쉽게 추가 할 수 있습니다.