关于Java多态性和转换的问题

时间:2009-07-04 15:48:27

标签: java casting polymorphism

我有一个班级C.班级E扩展了它。

E e = new E();
C c = new C();

为什么

e = (E) c;

进一步审核:虽然数字转换与转换对象具有相同的语法,但却出现了一些混淆。在任何情况下,上面都没有给出编译,而是给出了运行时错误 - 所以在某些实例中可以将类转换为子类(否则代码将无法编译)。任何人都可以提供以上工作的例子吗?

还有:

K extends M

K k = new K();

((M) k).getClass()给出K。这是为什么?它被转换为更通用的M

假设我在M和K中都实现了doIt()方法。正在执行

((M) k).doIt();

给出M或K的doIt()?

谢谢!

9 个答案:

答案 0 :(得分:15)

考虑一个现实世界的例子:

public class Dog extends Animal

所有的狗都是动物,但不是所有的动物都是狗。因此...

public class Cat extends Animal

只有当动物确实是狗时,才能将动物施放给狗。否则它会迫使宇宙推断出一只狗独有的属性(摇尾巴,吠叫等)到动物身上。动物可能是一只具有独特属性的猫(咕噜咕噜,严格的自我清洁等等)。如果无法进行强制转换,则会在运行时抛出ClassCastException。

没有人想要一只发出咕噜声的狗。


  

((M)k).getClass()给出K.为什么?它被铸造给更一般的M!

您已将k转换为M,但所有类都有getClass()方法。 k的等级总是K,无论你是否将其引用给M。如果你把一只狗扔给一只动物并问它它是什么动物它仍会回答它是一只狗。

事实上,投射到超类是多余的。狗已经是动物,它拥有动物的所有方法以及它自己的方法。许多代码分析工具(如FindBugs)会通知您冗余的强制转换,以便您可以删除它们。


  

假设我在M和K中都实现了doIt()方法。正在执行

     

((M)k).doIt();

     

给出M或K的doIt()?

K's doIt()与上述原因相同。演员在参考上运作;它不会将对象转换为其他类型。


  

你能举出一个铸造(狗狗狗=(狗)myAnimal)有意义的例子吗?

当然可以。想象一下接收动物列表进行处理的方法。所有的狗都需要散步,所有的猫都需要使用鸟形玩具。为此,我们调用仅存在于Dog上的takeForWalk()方法,或仅存在于Cat上的play()方法。

public void amuseAnimals( List<Animal> animals ) {
    for ( Animal animal : animals ) {
         if ( animal instanceof Dog ) {
             Dog doggy = (Dog)animal;
             doggy.takeForWalk( new WalkingRoute() );
         } else if ( animal instanceof Cat ) {
             Cat puss = (Cat)animal;
             puss.play( new BirdShapedToy() );
         }
     }
}

答案 1 :(得分:8)

您无法使用Java转换对象。

您可以使用Java转换引用。

转换引用不会改变它引用的对象的任何内容。它只生成一个指向与初始引用相同的对象的不同类型的引用。

转换原始值与转换引用不同。在这种情况下,值 do 会发生变化。

答案 2 :(得分:7)

因为E扩展了C,C不会成为E ...另一方面,E是C

编辑:在下面展开马克的评论......仅仅因为每个女人都是人,而不是所有人都是女人。所有人都与腿,手,脸等共享“人机界面”。当你提供钻石和黄金时,女性将其扩展到能够带来良好感受的功能。

int =&gt;双重转换甚至不相关,因为它不是类转换,而是转换告诉编译器存储y中的x中的任何内容(恰好是双精度)。

  

((M)k).getClass()给出K。

因为k仍然是K,即使你把它投射到M或对象(或恰好是其他东西)。

编辑:我认为这里的混乱是因为你认为k在你施放它时“成为”M,但事实并非如此。你只是把它当作一个M. 如果你问一个“狗主人”是什么样的品种,他将不会返回“它是一只狗”,原因很简单,就是在子类LabradorOwner中重写了getBreedName()方法以返回“拉布拉多犬”。它与getClass()相同,它将返回实现的类。它不会是M而是K也恰好是M也只是因为K延伸M。

答案 3 :(得分:4)

int / double无关;这是转化,而非转换 - intdouble之间没有任何关系。

回答问题;类型的对象在创建时是固定的。 <{1}} 对象不是(永远不会是)C。但是,您可以E视为E,因为继承代表“是一个”。例如:

C

这里我们仍然只有一个对象 - 只是E e = new E(); C c = e; 变量将其视为c,因此不会公开特定于C的方法(即使对象< strong> 一个E)。

如果我们再添加:

E

这是一种类型检查;再次,我们没有更改对象,但是将E secondE = (E) c; 放入c变量需要我们向编译器/运行时证明它确实一个{{1 }}。我们在第一个示例中不需要这个,因为它已经证明任何E也是E

同样,使用E - 所有强制转换都会改变编译器对对象的看法;你没有改变对象本身。它仍然是C

您需要将变量对象分开。演员正在谈论变量;他们不会改变对象。

答案 4 :(得分:1)

要添加到Frederik的答案中,将对象转换为某个对象并不会改变它的类型。此外,对象只能转换为它已经存在的类型(编译器当时不知道) 这就是为什么永远不会接受不可能的演员阵容:

Integer i = (Integer) new String();

不会编译,因为编译器知道它不可能。

答案 5 :(得分:1)

  

((M)k).getClass()给出K.为什么?它被铸造给更一般的M!

一个有用的类比(我从Bill Venners的网站artima.com获得)可能有助于消除混淆,即类和对象之间的差异就像建筑师的蓝图和建造的实际房屋之间的差异。蓝图存在于纸上,是一个概念,而房子存在于现实生活中。您可以根据同一蓝图建造多个房屋。

这与这个问题有什么关系?假设有McMansion蓝图和McMansionWithHeatedPool蓝图。 McMansionWithHeatedPool是带有加热池的McMansion的扩展名。

现在,如果您看到该对象的真实McMansionWithHeatedPool

  1. 从概念上讲(即,如果你看一下建筑师的蓝图),你会发现McMansionWithHeatedPool显然也是McMansion。因此允许上调。 (出于同样的原因,McMansion对象不能被类型转换为McMansionWithHeatedPool:没有加热池!)

  2. ((McMansion)k).getClass()给出McMansionWithHeatedPool因为k仍然是McMansionWithHeatedPool。类型转换是在表达式上,而不是在对象上。

答案 6 :(得分:1)

  

“如果编译器将其视为M,则应执行M的方法。”
  编译器将引用视为M.引用指向的实例是K类型,而不是M.您不能转换引用并假设这意味着实例会突然改变行为。编译器的作用是确保您在指定引用上调用的方法存在。它与调用哪个实现没有任何关系,只是存在实现。

答案 7 :(得分:0)

对于第一个问题,您不能将超类强制转换为子类,因为子类会添加超类没有的成员。编译器应该如何知道在它投射时放置哪些值?基本上,E是C,但C不是E。

getClass()获取内存中对象的类型。施放到M只是隐藏了它是K的事实,它不会改变底层对象。

答案 8 :(得分:0)

转换对象不会将对象更改为正在转换的对象,但允许通过继承与其相关的另一个类引用来引用该对象。

例如C extends E。他们都有一个方法myName();。如果你说

E e = new C();
e.myName();

您正在调用C myName()方法,如果您还要说

E e = new E();
C c = (C)e;

您刚刚告诉编译器它应该允许您使用E引用类型引用C