java - 为什么 Java 需要显式向下转型?

标签 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
}

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

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

或者,(我想我现在明白了)当我要求存储动物时,我实际上有可能得到一头Horse,因为作为父对象的 Animal 可以容纳其任何子类型。如果是这样的话,那么为什么 Java 允许 Animal 保存子类型,因为可能存在子类型的典型行为,例如 Dogbark(),为此,编译器必须再次检查并报告。我知道规则只是试图从最简单的意义上推理出来。

最佳答案

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

示例:

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

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

因此,通过这种有点复杂的表示法,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();
    }
}

现在你立刻就看到了你的问题。这段代码,不会运行。正确使用它就可以避免前面的问题。

如果您将 Animal 类设为abstract(您应该这样做),您将看到只能实例化特定的动物,而不能实例化一般的动物。之后,您将在需要时开始使用特定的类,并放心,您可以在使用通用类时重用一些代码。

背景

从概念上讲,运行时错误比编译时错误更难发现和调试。就像,真的很难。 (在 Stack Overflow 上搜索 NullPointerException,您会看到数百人在努力修复运行时异常)

在事物的层次结构中(一般来说,与编程无关),您可以有一些通用的“那是动物”。您还可以有一些特定的“那是一只狗”。当有人谈论一般性的事情时,你不能期望知 Prop 体的事情。动物不能向树吠叫,因为鸟不能,猫也不能。

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

您的具体情况

您假设:

Dog dog = (Dog) new Animal();

应该可以,因为狗是动物。但事实并非如此,因为并非所有动物都是狗。

但是:

Animal an = new Dog();

有效,因为所有狗都是动物。在这个具体案例中

Animal an = new Dog();
Dog dog = (Dog)an;

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

Animal an = new Pigeon();
Dog dog = (Dog)an;

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

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

关于java - 为什么 Java 需要显式向下转型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36383176/

相关文章:

java - 排列 JSON 对象中的元素?

c++ - 如何创建全部继承自一个基类的对象列表

c++ - 使用派生 [C++] 生成的参数初始化基类

c# - 将 0.0 转换为 double 有什么问题?

objective-c - int32_t 到 NSInteger 转换有什么问题吗?

java - 比较多个字符串时切换大小写与 If, else

Java(数百、数千等)

java - 我想将最终变量初始化为函数

java - 调用父类(super class)方法而不是子类方法

swift - Any 在 Swift 中的转换失败?协议(protocol)