编译时和运行时的Downcasting / Upcasting错误?
请检查以下程序。
我怀疑编译器何时会在编译器级别发出转换exception以及什么时候它将在runtime
?
喜欢下面的程序,表达
我假设(Redwood) new Tree()
应该在编译时失败,因为Tree不是Redwood。 但它在compile time
没有失败,正如预期的那样,它在runtime
失败了!
public class Redwood extends Tree { public static void main(String[] args) { new Redwood().go(); } void go() { go2(new Tree(), new Redwood()); go2((Redwood) new Tree(), new Redwood()); } void go2(Tree t1, Redwood r1) { Redwood r2 = (Redwood)t1; Tree t2 = (Tree)r1; } } class Tree { }
编译器只会查看表达式的编译时类型。 它不对表达式的运行时类型进行假设。 new Tree()
有编译时类型Tree
,所以(Redwood)new Tree()
与(Redwood)myTreeVariable
没什么区别。
Tree不一定是Redwood,但它可能是,因此缺少编译时错误。
在这种情况下,很明显它不是,但编译器倾向于抱怨绝对不正确的事情,而不是可能或可能不正确。 在这种情况下逻辑是不正确的,而不是代码本身,逻辑是你的问题,而不是编译器。
Tree t = new Redwood(); Redwood r = (Redwood) t;
在编译和运行时都完美有效。
我在我的解释中又添加了一个子类。
Tree / \ / \ / \ Redwood Blackwood
在此层次结构中:
Upcast :当我们沿着类层次结构在from the sub classes towards the root
的方向上转换引用时。 在这种情况下,我们不需要使用强制转换运算符
大写示例:
Redwood
或Blackwood
都是tree
:所以
Tree t1; Tree t2; Redwood r = new Redwood() ; Blackwood b = new Blackwood() ; t1 = r; // Valid t2 = b; // Valid
Downcast :当我们沿着类层次结构在from the root class towards the children or subclasses
的方向上转换引用时。 我们需要显式地输入。
垂头边的例子:
Redwood r = new Tree(); //compiler error, because Tree is not a Redwood Blackwood r = new Tree(); //compiler error, because Tree is not a Blackwood
如果它真的指向 Redwood
或Blackwook
对象, 则需要显式地键入cast一个Tree object
否则它在运行时会出错。 例如
在这种情况下:
Tree t1; Tree t2; Redwood r = new Redwood() ; Blackwood b = new Blackwood() ; t1 = r; // Valid t2 = r; // Valid Redwood r2 = (Redwood)t1; Blackwood b2 = (Blackwood)t2
[回答]
Redwood r = (Redwood) new Tree();
为什么没有编译错误?
Downcast的例子:
源Redwood r = (Redwood) new Tree();
拳头创建Tree对象并将类型转换为Redwood
。
你可以这样认为:
Tree t = new Tree();` Redwood r = (Redwood)t;
所以它在编译时没问题,
[回答]
为什么运行时错误?
但真正的Redwood
子类不能指向Tree
supper类对象。 所以你的代码在运行时失败了。
树t =新树();
t
指向Tree()
对象而不是Redwood()
。 这是运行时错误的原因。
编译器现在没有什么是t
值,并且语法上每个东西都是写的。 但是在运行时由于Redwood r = (Redwood)t;
,其中t
是Tree class
的对象,如果出现故障。
[建议:]
我想建议你使用instanceof运算符:
转换/强制操作用于在类型之间进行转换,而instanceof运算符用于在运行时检查类型信息 。*
说明 :
instanceof运算符允许您确定对象的类型。 它接受运算符左侧的对象和运算符右侧的类型,并返回一个布尔值,指示对象是否属于该类型。 通过一个例子可以最清楚地certificate这一点:
if (t instanceof Redwood) { Redwood r = (Redwood)t; // rest of your code }
但我还要补充一点: 从基类型转换为派生类型是一件坏事。 参考 : 小心instanceof运算符
我怀疑编译器何时会在编译器级别发出转换exception以及什么时候它将在运行时?
严格地说,编译器不会“发出转换exception”。 你会得到:
- 编译错误,或
- 运行时exception。
(Redwood) new Tree()
给出运行时exception而不是编译错误的原因是JLS(第5.5.1节)所说的应该发生的事情。
具体来说,JLS说:
“给定编译时引用类型S(源代码)和编译时引用类型T(目标),如果由于以下规则而没有发生编译时错误,则从S到T存在转换转换。”
“如果S是类类型” AND “如果T是类类型,则| S | <:| T |或| T | <:| S |。否则,发生编译时错误。”
哪里“| S | <:| T |” 表示类型S是T型或T的子类型。
在这种情况下,S是Tree
,T是Redwood
,两者都是类, Redwood
是Tree
的子类型……所以没有编译错误。
很明显,这是“错误的”这一事实并不重要。 JLS说它是合法的Java,因此它不应该给出编译错误。 (一个聪明的编译器可能会发出一个编译警告,表达式总是抛出exception……但这是一个不同的问题。)
JLS规则背后的原因并没有在规范中详细说明,但我认为它是这样的:
比较这三个片段:
Redwood r = (Redwood) new Tree(); Tree t = new Tree(); Redwood r = (Redwood) t; Tree t1 = new Tree(); Tree t2 = new Redwood(); Redwood r = (Redwood) (someCondition ? t1 : t2); Tree t = gardenStore.purchaseTree(); Redwood r = (Redwood) t;
假设他们将第一个片段定义为编译错误:
-
那第二个怎么样? 那也很容易certificate。
-
第三个怎么样? 这可能很容易……或者可能非常困难。
-
第四个怎么样? 现在片段的合法性取决于我们甚至可能没有源代码的方法的语义!
关键是,一旦你开始要求编译器certificate关于表达式的动态值的事情,你就会陷入滑坡,导致停止问题。 如果你使编译错误成为可选的,那么你会遇到一个可怕的情况:一个编译器可以说程序是有效的,另一个可以说它有错误!
引用的Tree可能事先是Redwood,这就是代码编译得很好的原因。
Tree t1 = new Redwood(); Redwood r1 = (Redwood)t1; Tree t2 = (Tree)r1;
你可以看到树()可能已经是红木了。
有必要补充说,不需要向下转换:由于Redwood也是一个树,你总是可以将它分配给一个树变量,
Redwood r; Tree t = r; // no casting needed
您可能总是在Java类型系统中寻找替换原则 。 我确信这些材料都是千兆吨。