从超类到子类的显式转换

发布于 2024-10-15 06:39:42 字数 364 浏览 3 评论 0原文

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

赋值 Dogdog = (Dog)animal; 不会生成编译错误,但在运行时它会生成 ClassCastException。为什么编译器无法检测到这个错误?

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

The assignment Dog dog = (Dog) animal; does not generate a compilation error, but at runtime it generates a ClassCastException. Why can't the compiler detect this error?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

时光与爱终年不遇 2024-10-22 06:39:42

通过使用强制转换,您实际上是在告诉编译器“相信我。我是专业人士,我知道我在做什么,并且我知道虽然您不能保证这一点,但我告诉您这个 Animal 变量肯定是一只狗。”

由于该动物实际上并不是一只狗(它是一只动物,您可以执行 Animal Animal = new Dog(); 它会是一只狗),因此虚拟机会在运行时抛出异常,因为您'已经违反了这种信任(你告诉编译器一切都会好,但事实并非如此!)

如果你尝试将对象转换为不同的继承层次结构(例如将 Dog 转换为 String),编译器比盲目接受一切要聪明一些。然后编译器会把它扔回给你,因为它知道这永远不可能工作。

因为您本质上只是阻止编译器抱怨,所以每次进行强制转换时,务必在 if 语句中使用 instanceof 来检查是否不会导致 ClassCastException (或类似的东西。)

By using a cast you're essentially telling the compiler "trust me. I'm a professional, I know what I'm doing and I know that although you can't guarantee it, I'm telling you that this animal variable is definitely going to be a dog."

Since the animal isn't actually a dog (it's an animal, you could do Animal animal = new Dog(); and it'd be a dog) the VM throws an exception at runtime because you've violated that trust (you told the compiler everything would be ok and it's not!)

The compiler is a bit smarter than just blindly accepting everything, if you try and cast objects in different inheritence hierarchies (cast a Dog to a String for example) then the compiler will throw it back at you because it knows that could never possibly work.

Because you're essentially just stopping the compiler from complaining, every time you cast it's important to check that you won't cause a ClassCastException by using instanceof in an if statement (or something to that effect.)

溺渁∝ 2024-10-22 06:39:42

因为理论上动物可以是一只狗:

Animal animal = new Dog();

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

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

Because theoretically Animal animal can be a dog:

Animal animal = new Dog();

Generally, downcasting is not a good idea. You should avoid it. If you use it, you better include a check:

if (animal instanceof Dog) {
    Dog dog = (Dog) animal;
}
浪荡不羁 2024-10-22 06:39:42

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

In order to avoid this kind of ClassCastException, if you have:

class A
class B extends A

You can define a constructor in B that takes an object of A. This way we can do the "cast" e.g.:

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
}
心不设防 2024-10-22 06:39:42

详细阐述 Michael Berry 给出的答案。

Dog d = (Dog)Animal; //编译但在运行时失败

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

编译器只知道声明的引用类型。 JVM 在运行时知道该对象到底是什么。

因此,当 JVM 在运行时发现 Dog d 实际上指的是 Animal 而不是 Dog 对象时,它就会说。
嘿...你对编译器撒了谎并抛出了一个巨大的ClassCastException

因此,如果您要进行向下转型,则应该使用 instanceof 测试来避免搞砸。

if (狗的动物实例) {
狗狗=(狗)动物;
现在我们

想到一个问题。为什么编译器最终会抛出 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; // 错误:无法将 Dog 转换为 String

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

Dog d = new Dog();
动物animal1 = d; // 无需显式强制转换即可正常工作
动物animal2 = (动物) d; // 使用 n 显式强制转换可以正常工作

上面的两种向上强制转换都可以正常工作,没有任何异常,因为狗是动物,动物能做的任何事,狗都能做。但反之则不然。

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.

全部不再 2024-10-22 06:39:42

制定@Caumons 的答案:

想象一个父亲类有很多孩子,并且需要添加一个公共的
字段进入该类。如果您考虑上述方法,您应该
逐一转到每个子类,并为新字段重构它们的构造函数。
因此,在这种情况下,该解决方案并不是一个有前途的解决方案

。现在看一下该解决方案。

父亲可以从每个孩子那里接收一个 self 对象。这里有一位父亲
类:

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

结果如下:

父字段是:父字符串

子字段是:子字符串

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

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;

水水月牙 2024-10-22 06:39:42

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

Animal animal=new Animal();

出于多种原因,Java 中不允许向下转型。
有关详细信息,请参阅此处

The code generates a compilation error because your instance type is an Animal:

Animal animal=new Animal();

Downcasting is not allowed in Java for several reasons.
See here for details.

月依秋水 2024-10-22 06:39:42

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

As explained, it is not possible.
If you want to use a method of the subclass, evaluate the possibility to add the method to the superclass (may be empty) and call from the subclasses getting the behaviour you want (subclass) thanks to polymorphism.
So when you call d.method() the call will succeed withoug casting, but in case the object will be not a dog, there will not be a problem

灰色世界里的红玫瑰 2024-10-22 06:39:42

正如之前所说,除非您的对象首先是从子类实例化的,否则您无法从超类转换为子类。

不过,也有解决方法。

您所需要的只是一组构造函数和一个方便的方法,该方法可以将您的对象强制转换为 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();
    }

}

As it was said before, you can't cast from superclass to subclass unless your object was instantiated from the subclass in the first place.

However, there are workarounds.

All you need is a set of constructors and a convenience method that will either cast your object to Dog, or return a new Dog object with the same Animal properties.

Below is an example that does just that:

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

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文