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

我有一个class级C.class级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()?

谢谢!

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

 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的doIt()与上述原因相同。 演员在参考上运作; 它不会将对象转换为其他类型。


你能举一个铸造时的例子(Dog doggy =(Dog)myAnimal)有意义吗?

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

 public void amuseAnimals( List 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() ); } } } 

您无法在Java中强制转换对象。

您可以使用Java转换引用。

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

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

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

编辑:扩展马克在下面的评论……仅仅因为每个女人都是人,而不是所有人都是女人。 所有人都与腿,手,脸等共享“人机界面”。当你提供钻石和黄金时,女性将其延伸至具有回报良好感觉的function。

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

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

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

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

int / double是无关的; 这是转换,而不是转换 – intdouble之间没有关系。

回答问题; 类型的对象在创建时是固定的。 作为C对象 不是 (并且永远不会是) E 。 但是,您可以 E视为C ,因为inheritance表示“是一个”。 例如:

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

这里我们仍然只有一个对象 – 只是c变量将其视为C ,因此不会暴露特定于E方法(即使对象 E )。

如果我们再添加:

 E secondE = (E) c; 

这是一种类型检查; 再一次,我们没有改变对象,但是将c放入E变量需要我们向编译器/运行时certificate它确实是 E 我们在第一个例子中并不需要这个,因为它已经certificate任何E也是C

同样,使用getClass() – 所有getClass()转换都会改变编译器对对象的看法; 你没有改变对象本身。 它仍然是K

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

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

 Integer i = (Integer) new String(); 

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

((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 。 类型转换是在表达式上,而不是在对象上。

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

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

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

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

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

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

你正在调用C myName()方法,如果你也说

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

你刚刚告诉编译器它应该允许你引用具有C引用类型的E