[英]Question about Java polymorphism and casting
我有一个 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()?
谢谢!
考虑一个现实世界的例子:
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<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() );
}
}
}
您不能在 Java 中转换对象。
您可以在 Java 中转换引用。
转换引用不会改变它所引用的对象的任何内容。 它只生成指向与初始引用相同的对象的不同类型的引用。
转换原始值与转换引用不同。 在这种情况下,值确实会发生变化。
仅仅因为 E 扩展了 C,C 不会变成 E... E 是 C
编辑:扩大马克在下面的评论......仅仅因为每个女人都是人,并不是所有的人都是女人。 所有人都与腿、手、脸等共享“人机界面”。女性将其扩展为功能,当您提供钻石和黄金时,这些功能会带来良好的感觉。
int => double 转换甚至不相关,因为它不是类转换,而是一种转换,它告诉编译器将 x 中的任何内容存储在 y 中(恰好是双精度)。
((M) k).getClass() 给出 K。
因为即使将 k 转换为 M 或对象(或碰巧是其他东西), k 仍然是 K。
编辑:我认为这里的混淆是因为你认为 k 在你施放时会“变成”一个 M,但事实并非如此。 你只是把它当作一个M。如果你问一个“狗主人”它是什么品种,他不会返回“这是一只狗”,原因很简单,getBreedName()方法很可能有在子类 LabradorOwner 中被覆盖以返回“拉布拉多”。 与 getClass() 相同,它会返回实现的类。 它不会是一个 M,而是一个恰好也是一个 M 的 K,仅仅因为 K 扩展了 M。
int
/ double
是无关的; 那是转换,而不是强制转换 - int
和double
之间没有关系。
重新提问; 类型的对象在创建时固定。 作为C
的对象不是(并且永远不可能是) E
。 但是,您可以将E
视为C
,因为继承代表“是一个”。 例如:
E e = new E();
C c = e;
这里我们仍然只有一个对象 - 只是c
变量将其视为C
,因此不会公开特定于E
方法(即使该对象是E
)。
如果我们再添加:
E secondE = (E) c;
这是一个类型检查; 同样,我们没有更改对象,但是将c
放入E
变量需要我们向编译器/运行时证明它确实是一个E
。 我们在第一个例子中不需要这个,因为它已经可以证明任何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
,对于那个对象:
从概念上讲(即,如果您查看架构师的蓝图),您会发现McMansionWithHeatedPool
显然也是McMansion
。 因此允许向上转换。 (出于同样的原因,不能将McMansion
对象类型转换为McMansionWithHeatedPool
:没有加热池!)
(( McMansion
)K).getClass()给出McMansionWithHeatedPool
因为k是仍然一个McMansionWithHeatedPool
。 类型转换是在表达式上,而不是在对象上。
“如果编译器将其视为 M,则它应该执行 M 的方法。”
编译器将引用视为 M。引用指向的实例类型为 K,而不是 M。您不能强制转换引用并假设这意味着实例会突然改变行为。 编译器所做的是确保您对指定引用调用的方法存在。 它与调用哪个实现没有任何关系,只是一个实现确实存在。
对于第一个问题,您不能将超类转换为子类,因为子类添加了超类没有的成员。 编译器如何知道在转换时要放置哪些值? 基本上,E是C,但C不是E。
getClass() 获取内存中对象的类型。 强制转换为 M 只是隐藏了它是 K 的事实,它不会改变底层对象。
强制转换对象不会将对象更改为被强制转换的对象,而是允许通过继承与其相关的另一个类引用来引用该对象。
例如C extends E
。 他们都有一个方法myName();
. 如果你说
E e = new C();
e.myName();
您正在调用C myName()
方法,如果您还说
E e = new E();
C c = (C)e;
您只是告诉编译器它应该允许您使用C
引用类型来引用E
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.