从超类到子类的显式转换

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。为什么编译器无法检测到这个错误?

265852 次浏览

因为理论上 Animal animal 可以是一只狗:

Animal animal = new Dog();

一般来说,沮丧不是一个好主意。你应该避免它。如果你使用它,你最好包括一个检查:

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

代码生成一个编译错误,因为您的 实例类型是动物:

Animal animal=new Animal();

由于以下几个原因,在 Java 中不允许向下转换。 详情请参阅 给你

通过使用强制转换,您实际上是在告诉编译器“相信我。我是专业人士,我知道自己在做什么,我知道尽管你不能保证,但我告诉你,这个 animal变量肯定是一条狗。”

因为动物实际上不是狗(它是动物,你可以做 Animal animal = new Dog();,它会是狗) VM 在运行时抛出一个异常,因为你违反了信任(你告诉编译器一切都会好的,但它不是

编译器比盲目地接受所有东西要聪明一些,如果你尝试在不同的继承层次结构中强制转换对象(例如,强制转换狗到字符串) ,编译器会把它扔回给你,因为它知道这是不可能工作的。

因为您实际上只是在阻止编译器发出抱怨,所以每次进行强制转换时,一定要检查在 if 语句中使用 instanceof是否会导致 ClassCastException(或者类似的情况)

为了避免这种 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
}

阐述 Michael Berry 给出的答案。

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

这里你对编译器说: “相信我。我知道 d实际上指的是 Dog对象”,尽管它不是。 请记住,当我们执行向下转换 时,编译器被迫信任我们。

编译器只知道声明的引用类型,而运行时的 JVM 知道对象实际上是什么。

因此,当 JVM 在运行时发现 Dog d实际上引用的是 Animal而不是它所说的 Dog对象时。 嘿... 你对编译器撒了谎,扔了个大大的 ClassCastException

因此,如果你正在下降,你应该使用 instanceof测试,以避免搞砸。

如果(狗的动物实例){ 狗狗 = (狗)动物; }

现在我们想到一个问题。为什么该死的编译器在最终抛出 java.lang.ClassCastException的时候允许向下转换?

答案是,编译器所能做的就是验证这两种类型是否在同一个继承树中,因此取决于代码可能具有的内容 在下降之前,有可能 animaldog型。

编译器必须允许在运行时可能工作的内容。

考虑下面的代码片段:

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;
}

但是,如果编译器确信强制转换不可能工作,则编译将失败。也就是说。如果尝试在不同的继承层次结构中强制转换对象

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

与向下转换不同,向上转换是隐式工作的,因为当您向上转换时,您隐式地限制了可以调用的方法的数量, 与向下转换相反,这意味着以后可能需要调用更具体的方法。

狗 d = 新狗() ; 动物1 = d;//没有明确的演员阵容也能很好地工作 动物动物2 = (动物) d;//使用 n 显式转换可以很好地工作

以上两种方式都可以很好的工作,没有任何例外,因为狗是一种动物,任何动物可以做的事情,狗都可以做。但反之亦然。

如前所述,这是不可能的。 如果你想使用子类的一个方法,评估一下把这个方法添加到超类中的可能性(可能是空的) ,然后从子类中调用,通过多态性获得你想要的行为(子类)。 因此,当您调用 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);
}
}

结果是这样的:

菲尔德神父是: 神父绳

子字段为: 子字符串

现在您可以轻松地向父类添加新字段,而不用担心您的子代码会被破解;

如前所述,除非您的对象首先是从子类实例化的,否则不能从超类强制转换到子类。

不过,也有变通方法。

您所需要的只是一组构造函数和一个方便的方法,这个方法要么将对象强制转换为 Dog,要么返回一个具有相同 Animal 属性的新 Dog 对象。

下面的例子就是这样做的:

public class Animal {


public Animal() {}


public Animal(Animal in) {
// Assign animal properties
}


public Dog toDog() {
if (this instanceof Dog)
return (Dog) this;
return new Dog(this);
}


}


public class Dog extends Animal {


public Dog(Animal in) {
super(in);
}


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


}