编译时和运行时的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的方向上转换引用时。 在这种情况下,我们不需要使用强制转换运算符

大写示例:

RedwoodBlackwood都是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 

如果它真的指向 RedwoodBlackwook对象, 需要显式地键入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; ,其中tTree 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 ,两者都是类, RedwoodTree的子类型……所以没有编译错误。

很明显,这是“错误的”这一事实并不重要。 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类型系统中寻找替换原则 。 我确信这些材料都是千兆吨。