4

请检查以下程序。

我怀疑编译器何时会在编译器级别发出强制转换异常以及何时会在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 { }
4

6 回答 6

6

树不一定是红木,但它可能是,因此没有编译时错误。

在这种情况下,很明显它不是,但是编译器倾向于抱怨绝对不正确的事情,而不是可能或可能不正确的事情。在这种情况下,逻辑不正确,而不是代码本身,逻辑是您的问题,而不是编译器的问题。

Tree t = new Redwood();
Redwood r = (Redwood) t;

在编译和运行时都完全有效。

于 2013-01-06T09:33:50.703 回答
5

我在解释中又添加了一个子类。

       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  

Tree object 如果它确实指向 Redwood对象或对象,则需要显式类型转换,否则Blackwook在运行时会出错。例如

在这种情况下:

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超级类对象。所以你的代码在运行时失败。

树 t = 新树();

t指向Tree()对象 not Redwood()。这就是运行时错误的原因。

编译器现在不写什么是有价值的,t并且在语法上每件事都是写的。但是在运行时由于,if的对象Redwood r = (Redwood)t;在哪里出错了。 tTree class

[建议:]

我建议您使用instanceof运算符:

强制转换/强制操作用于在类型之间进行转换,instanceof运算符用于在运行时检查类型信息。*

说明

instanceof运算符允许您确定对象的类型。它接受运算符左侧的对象和运算符右侧的类型,并返回一个布尔值,指示该对象是否属于该类型。举个例子最清楚地说明了这一点:

if (t instanceof Redwood)
{
    Redwood r = (Redwood)t;
    // rest of your code
}

但我还要补充一点:从基类型转换为派生类型是一件坏事。 参考当心 instanceof 运算符

于 2013-01-06T09:58:40.967 回答
5

编译器只会查看表达式的编译时类型。它不对表达式的运行时类型进行假设。new Tree()具有编译时类型Tree,因此(Redwood)new Tree()(Redwood)myTreeVariable.

于 2013-01-06T10:02:13.283 回答
2

我怀疑编译器何时会在编译器级别发出强制转换异常以及何时会在运行时发出异常?

严格来说,编译器不会“发出强制转换异常”。您将获得:

  • 编译错误,或
  • 运行时异常。

(Redwood) new Tree()给出运行时异常而不是编译错误的原因是JLS (第 5.5.1 节)所说的应该发生。

具体来说,JLS 是这样说的:

“给定一个编译时引用类型 S(源)和一个编译时引用类型 T(目标),如果由于以下规则而没有发生编译时错误,则存在从 S 到 T 的强制转换。”

“如果 S 是类类型” AND “如果 T 是类类型,则 |S| <: |T| 或 |T| <: |S|。否则,会发生编译时错误。”

其中“|S| <: |T|” 表示类型 S 是类型 T 或 T 的子类型。

在这种情况下 S isTree和 T is Redwood,两者都是类并且RedwoodTree... 的子类型,因此没有编译错误。

很明显这是“错误”的事实是不相关的。JLS 说它是合法的 Java,因此它不应该给出编译错误。(智能编译器可能会发出编译警告,表示表达式将始终抛出异常......但这是一个不同的问题。)


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;

假设他们将第一个片段定义为编译错误:

  • 第二个呢?嗯,这也很容易证明。

  • 第三个呢?这可能很容易……或者可能非常困难。

  • 第四个呢?现在片段的合法性取决于我们甚至可能没有源代码的方法的语义!

关键是,一旦您开始要求编译器证明表达式的动态值,您就处于导致停机问题的滑坡上。如果你将编译错误设为可选,那么你会遇到可怕的情况,一个编译器可以说一个程序是有效的,而另一个编译器可以说它有错误!

于 2013-01-06T10:55:29.277 回答
0

需要补充的是,不需要向下转换:由于Redwood也是一棵树,所以你总是可以将它赋值给一个树变量,

Redwood r;
Tree t = r; // no casting needed

您可能总是在 Java 类型系统中寻找替代原则。我敢肯定,有十亿吨的材料。

于 2013-01-06T09:42:50.873 回答
0

引用的树可能事先是红木,这就是代码编译良好的原因。

     Tree t1 =  new Redwood(); 
     Redwood r1 = (Redwood)t1;
     Tree t2 = (Tree)r1;

您可以看到 Tree() 可能已经是红木了。

于 2013-01-06T10:09:33.680 回答