为什么 Java 需要显式向下转换?

xpl*_*raj 1 java inheritance casting

我看过类似问题的其他答案,但所有答案都依赖于语言被定义为这样的事实。以下是我寻求解释的内容:

在继承层次结构中,父类型可以隐式地保存子对象(为什么?),但是对于子引用来保存父对象,显式向下转换是必要的(为什么?)。

请举一些例子来解释为什么不这样做会失败,我的意思是使用动物、狗类型等。如果这个问题已经得到回答并且我错过了它,引用它也会有帮助。

例如:

class Animal{
    public void eat(){};
}

class Dog extends Animal{
    public void bark(){};
}

class App{
    Animal an = new Dog(); //1. why can animal store a dog but not a dog store an animal
    Dog dog = (Dog) new Animal(); //2. Dog has the behavior of an Animal so why explicit downcast
}
Run Code Online (Sandbox Code Playgroud)

我只想知道这些行是如何有意义的,而不仅仅是知道它们是语言语义。如果您的回答看起来像一位奶奶向她的孙子解释这一点,那就更好了。

编辑: 我只是想知道Dog继承Animal并拥有所有行为。因此,上面的数字 2 应该被允许而无需显式向下转换。

或者,当我要求 aDog存储 an时(我想我现在明白了)Animal有可能我实际上得到了 aCow或 a Horse,因为Animal作为父级可以保存它的任何子类型。如果是这种情况,那么为什么 Java 允许Animal保留子类型,因为可能存在子类型的典型行为,例如Dogwill bark(),因为编译器必须再次检查和报告。我知道规则只是试图从最简单的意义上进行推理。

Ang*_*chs 5

Java 中严格类型绑定的好处是,如果可能,您会得到编译时错误,而不是运行时错误。

例子:

class Animal {
    void eat() {}
}
class Dog extends Animal  {
    void bark() {}
}
class Pigeon extends Animal {
    void pick() {}
}
class MyFunction {
    void run() {
       Animal first = new Pigeon();
       // the following line will compile, but not run
       ((Dog)first).bark();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您在像这样的简单示例中有这样的代码,您将立即发现问题。但是考虑在一个项目中遇到这样的问题,在数千行代码的深度,在数百个类中很少被调用的函数中。在生产中的某一天,代码失败并且您的客户很不高兴。由您来找出它失败的原因、发生了什么以及如何修复它。这是一项可怕的任务。

所以,Java 用这个有点复杂的符号促使你重新思考你的代码,下一个例子是它如何做得更好:

class MyFunction {
    void run() {
       Pigeon first = new Pigeon();
       // the following line will NOT compile
       first.bark();
       // and neither will this. Because a Pigeon is not a Dog.
       ((Dog)first).bark();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您立即看到您的问题。这段代码,不会运行。您可以通过正确使用它来避免前面的问题。

如果你创建你的Animalabstract(你应该这样做),你会看到你只能实例化特定的动物,而不能实例化一般的动物。之后,您将在需要时开始使用特定的,并且在使用通用类时可以重用一些代码,这让您感到宽慰。

背景

从概念上讲,运行时错误更难找到和调试,然后是编译时错误。喜欢,真的很难。(在 Stack Overflow 上搜索 NullPointerException,你会看到数百人在努力修复运行时异常)

在事物的层次结构中(一般来说,与编程无关)你可以有一些一般的“那是一种动物”。你也可以有一些特定的东西“那是一只狗”。当有人谈论一般的事情时,你不能指望知道具体的事情。动物不能叫树,因为鸟不能,猫也不能。

因此,特别是在 Java 中,最初的程序员发现决定您需要知道一个足够具体的对象来调用该对象的函数是明智的。这确保如果您不注意,编译器将警告您,而不是您的运行时。

你的特殊情况

你假设:

Dog dog = (Dog) new Animal();
Run Code Online (Sandbox Code Playgroud)

应该工作,因为狗是动物。但它不会,因为并非所有的动物都是狗。

但:

Animal an = new Dog();
Run Code Online (Sandbox Code Playgroud)

有效,因为所有的狗都是动物。在这种特殊情况下

Animal an = new Dog();
Dog dog = (Dog)an;
Run Code Online (Sandbox Code Playgroud)

也可以工作,因为该动物的特定运行时状态恰好是 Dog。现在如果你把它改成

Animal an = new Pigeon();
Dog dog = (Dog)an;
Run Code Online (Sandbox Code Playgroud)

它仍然会编译,但不会运行,因为第二行Dog dog = (Dog)an;失败。你不能把鸽子投给狗。

因此,在这种情况下,您将获得一个ClassCastException. 如果您尝试强制转换new Animal()为 Dog,则相同。动物不是狗。现在,这将在运行时发生,这很糟糕。在 Java 的思维方式中,编译时错误优于运行时错误。