169
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. 为什么编译器不能检测到这个错误?

4

7 回答 7

353

通过使用强制转换,您实际上是在告诉编译器“相信我。我是专业人士,我知道自己在做什么,而且我知道虽然您不能保证,但我告诉您这个animal变量绝对是会变成一条狗。”

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

编译器比盲目地接受所有东西要聪明一点,如果您尝试将对象转换为不同的继承层次结构(例如将 Dog 转换为 String),那么编译器会将其扔回给您,因为它知道这永远不可能工作。

因为您实际上只是阻止编译器抱怨,所以每次转换时,重要的是检查您不会在 if 语句中ClassCastException使用instanceof(或类似的东西)。

于 2011-02-01T13:23:35.127 回答
56

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

Animal animal = new Dog();

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

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
于 2011-02-01T13:13:11.000 回答
49

为了避免这种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
}
于 2012-02-12T21:54:58.513 回答
29

Elaborating the answer given by Michael Berry.

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

Here you are saying to the compiler "Trust me. I know d is really referring to a Dog object" although it's not. Remember compiler is forced to trust us when we do a downcast.

The compiler only knows about the declared reference type. The JVM at runtime knows what the object really is.

So when the JVM at the runtime figures out that the Dog d is actually referring to an Animal and not a Dog object it says. Hey... you lied to the compiler and throws a big fat ClassCastException.

So if you are downcasting you should use instanceof test to avoid screwing up.

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

Now a question comes to our mind. Why the hell compiler is allowing the downcast when eventually it is going to throw a java.lang.ClassCastException?

The answer is that all the compiler can do is verify that the two types are in the same inheritance tree, so depending on whatever code might have come before the downcast, it's possible that animal is of type dog.

The compiler must allow things that might possible work at runtime.

Consider the following code 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;
}

However, if the compiler is sure that the cast would not possible work, compilation will fail. I.E. If you try to cast objects in different inheritance hierarchies

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

Unlike downcasting, upcasting works implicitly because when you upcast you are implicitly restricting the number of method you can invoke, as opposite to downcasting, which implies that later on, you might want to invoke a more specific method.

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

Both of the above upcast will work fine without any exception because a Dog IS-A Animal, anithing an Animal can do, a dog can do. But it's not true vica-versa.

于 2014-06-23T13:07:57.133 回答
3

To develop the answer of @Caumons:

Imagine one father class has many children and there is a need to add a common field into that class. If you consider the mentioned approach, you should go to each children class one by one and refactor their constructors for the new field. therefore that solution is not a promising solution in this scenario

Now take a look at this solution.

A father can receive an self object from each children. Here is a father class:

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
}

Here is our child class that should implement one of its father constructor, in this case the aforementioned constructor :

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

Now we test out application:

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

And here is the result :

Father Field is: Father String

Child Field is: Child String

Now you can easily add new fields to father class without being worried of your children codes to break;

于 2018-11-23T05:24:44.857 回答
1

该代码会生成编译错误,因为您的实例类型是 Animal:

Animal animal=new Animal();

由于几个原因,Java 中不允许向下转换。有关详细信息,请参见此处

于 2011-02-01T13:22:53.430 回答
1

如前所述,这是不可能的。如果要使用子类的方法,请评估将方法添加到超类(可能为空)的可能性,并通过多态性从子类调用获得您想要的行为(子类)。因此,当您调用 d.method() 时,调用将成功而无需强制转换,但如果对象不是狗,则不会有问题

于 2015-02-18T14:57:31.783 回答