为什么Java需要明确的向下转换?

时间:2016-04-03 08:24:19

标签: 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。

或者,当我要求Dog存储Animal时,我认为(现在我知道了)我有可能实际获得Cow或{{1}因为父项Horse可以保存其任何子类型。如果是这种情况那么为什么Java允许Animal保持子类型,因为可能存在像Animal Dog这样的子类型的典型行为,因为编译器必须检查并报告。我知道规则只是试图从最简单的意义上推理出来。

5 个答案:

答案 0 :(得分:4)

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

如果您在这样的简单示例中有这样的代码,您将立即发现问题。但是考虑在项目中存在这样的问题,在数百个类中,在数千行代码的深度中很少被称为函数。在生产的一天,代码失败,您的客户感到不安。并由您决定它失败的原因,发生的事情以及如何解决它。这是一项糟糕的任务。

所以,有了这个有点复杂的表示法,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();
    }
}

现在你马上就能看到你的问题了。这段代码不会运行。您可以正确使用它来避免前面的问题。

如果你制作了Animalabstract(你应该这样做),你会看到你只能实例化特定的动物,而不是普通动物。之后,您将在需要时开始使用特定的,并且在使用普通类时可以重复使用某些代码。

背景

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

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

因此,特别是在Java中,原始程序员发现明智的做法是确定需要知道一个特定的对象来调用该对象的函数。这可以确保如果您没有注意,编译器将警告您,而不是运行时。

您的具体案例

你认为:

Dog dog = (Dog) new Animal();

应该有用,因为狗是动物。但它不会,因为不是所有的动物都是狗。

BUT:

Animal an = new Dog();

有效,因为所有狗都是动物。在这个特定情况下

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

也会起作用,因为该动物的特定运行时状态恰好是Dog。现在,如果你改为

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

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

因此,在这种情况下,您将获得ClassCastException。如果您尝试将new Animal()投射到Dog,则相同。动物不是狗。现在,这将在运行时发生,这很糟糕。在Java思维方式中,编译时错误比运行时错误更好。

答案 1 :(得分:2)

是。理解该理论的一个简单实时例子是

每个卡车司机都是司机,但不能说你不能说每个司机都是卡车司机。

其中

Driver -Parent
Truck Driver - Child.


Animal an = new Dog(); //1. why can animal store a dog but not a dog store an animal

你订购了一只动物,店主为你送了一只狗,你很开心因为狗是动物。

Dog do = (Dog) new Animal(); //2. Dog has the behavior of an Animal so why explicit downcast

你正在要求一只狗,而店主给你一只动物。所以很明显你检查动物是不是狗。不是吗?或者你只是假设你有一只狗?

思考。

答案 2 :(得分:2)

在java引用中使用。假设我们有以下类

class A{
    Integer a1;
    public A(){
        // a1 is initialized 
    }
    public void baseFeature(){
        super.baseFeature();
        // some extra code
    }
}

class B extends A{
    Integer b1;
    Integer b2;
    public B(){
        super();
       // b1 , b2 are initialized 
    }
    @Override
    public void baseFeature(){
        super.baseFeature();
        // some extra code
    }
    public void extraFeature(){
       // some new features not in base class
    }
}

以下所有3个陈述均有效

A a = new A();

B b = new B();

A b1 = new B();

在java中,引用用于引用Heap中保存的对象。

  1. 不应将A类型的引用视为无法保存比A类对象具有更多内存所需的对象。它是引用而不是对象持有者。
  2. 如果是子类型对象创建,则构造函数调用如下:父构造函数调用后跟构造函数调用正在创建其对象的实际类。
  3. 可以说子类对象的特征至少与父类型一样多。
  4. A b = new B()没有混淆,因为B的对象具有其父级的所有功能。 子类对象具有其父类中定义的所有功能,因此可以在子类的对象上调用任何父类方法
  5. 子类可以有更多的功能,父类没有,所以在父对象上调用子类的方法会导致问题
  6. 假设在Java B a = new A()中有效,那么如果调用a.extraFeature(),那么很明显是错误。

    这是由编译时错误阻止的。如果需要向下转换,那么程序员必须格外小心。这是编译时错误的意图。下面的案例下载不会导致问题,但程序员有责任看看情况是否属于这种情况。

    public void acceptChild(Child c){
        c.extraMethodNotWithParent();
    }
    Parent p = new Child();
    acceptChild((Child)p);
    

    如果没有进行压缩,程序员会收到编译时警告。程序员可以查看并查看实际对象是否真的属于子类类型,然后他/她可以进行显式向下转换。因此,只有在程序员没有注意的情况下才会出现问题。

答案 3 :(得分:0)

这是因为继承是特化,当你继承一个类T时,子类ST的特化,因为Java不需要一个演员,但T可以有很多孩子,S'也会继承TS',所以如果你有一个S的对象并且它被引用为{{} 1}}并且您希望再次获取原始类型,您应该转换为T并在运行时检查Java类型。

一个例子: 男人和女人都是人,但人是男人或女人。

S

如果Java确实已经实现了强制转换,如果没有,它会报告它并且不会在您的位置做出决定。此检查是在运行时进行的。

答案 4 :(得分:0)

这里的所有回复都有帮助。我很困惑。 除去我脑中的所有链条,如果我只是想,它现在看起来像:
父级可以保留子类型。
你的意思是多态性的好处及其实现方式 子类型可以保留父级,但需要明确向下转发。
由于允许多态性,这种垂头丧气是一种保障。要求编译器相信代码中的Animal实际上将是Dog类型想要持有的Dog实例。