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
。为什么编译器无法检测到此错误?
答案 0 :(得分:280)
通过使用演员你基本上告诉编译器“相信我。我是专业人士,我知道我在做什么,我知道虽然你不能保证,但我告诉你这个animal
变量肯定会成为一只狗。“
由于动物实际上不是一只狗(它是一种动物,你可以做Animal animal = new Dog();
并且它是一只狗)VM在运行时抛出异常,因为你违反了这种信任(你告诉过)编译器一切都会好的,但事实并非如此!)
编译器比仅仅盲目地接受所有内容更聪明一点,如果你尝试在不同的继承层次结构中强制转换对象(例如将一个Dog转换为一个String),那么编译器会把它扔回给你,因为它知道它永远不会可能会工作。
因为你实际上只是停止编译器的抱怨,所以每次你强制转换它都很重要,在if语句中使用ClassCastException
检查你是否会导致instanceof
。那种效果。)
答案 1 :(得分:44)
因为理论上Animal animal
可以是狗:
Animal animal = new Dog();
一般来说,向下转换不是一个好主意。你应该避免它。如果您使用它,最好包括一个支票:
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
}
答案 2 :(得分:38)
为了避免这种ClassCastException,如果你有:
class A
class B extends A
你可以在B中定义一个带有A对象的构造函数。这样我们可以做“强制转换”,例如:
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
}
答案 3 :(得分:21)
Dog d = (Dog)Animal; //Compiles but fails at runtime
在这里,你要对编译器说'#34;相信我。我知道d
实际上指的是Dog
对象"虽然不是。
请记住,当我们进行向下转换时,编译器必须信任我们。
编译器只知道声明的引用类型。运行时的JVM知道对象到底是什么。
因此,当运行时的JVM发现Dog d
实际上指的是Animal
而不是Dog
对象时。
嘿......你骗了编译器并抛出一个大胖子ClassCastException
。
因此,如果你是向下转发,你应该使用instanceof
测试来避免搞砸。
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
}
现在我们想到了一个问题。为什么地狱编译器在最终抛出java.lang.ClassCastException
时会允许向下转换?
答案是所有编译器都可以做的是验证这两种类型是否在同一继承树中,因此取决于可能具有的代码
在向下倾斜之前,animal
可能属于dog
类型。
编译器必须允许可能在运行时工作的东西。
考虑以下代码snipet:
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;
}
但是,如果编译器确定转换不可行,则编译将失败。 I.E.如果您尝试在不同的继承层次结构中转换对象
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动物,狗可以做。但事实并非如此,反之亦然。
答案 4 :(得分:3)
答案 5 :(得分:1)
如上所述,这是不可能的。 如果要使用子类的方法,请评估将方法添加到超类(可能为空)的可能性,并通过多态来从子类调用获取所需的行为(子类)。 因此,当您调用d.method()时,调用将成功进行转换,但如果对象不是狗,则不会出现问题
答案 6 :(得分:0)
开发@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);
}
}
这是结果:
父亲字段为:父亲字符串
子字段为:子字符串
现在,您可以轻松地向父亲班级添加新字段,而不必担心孩子的密码会被破坏;