请检查以下程序。
我怀疑编译器何时会在编译器级别发出转换异常,何时它将在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 { }
答案 0 :(得分:5)
Tree不一定是Redwood,但可能是,因此缺少编译时错误。
在这种情况下很明显它不是,但编译器倾向于抱怨绝对不正确的事情,而不是可能或可能不正确。在这种情况下逻辑是错误的,而不是代码本身,逻辑是你的问题,而不是编译器。
Tree t = new Redwood();
Redwood r = (Redwood) t;
在编译和运行时都完美有效。
答案 1 :(得分:4)
编译器只会查看表达式的编译时类型。它不对表达式的运行时类型进行假设。 new Tree()
的编译时类型为Tree
,因此(Redwood)new Tree()
与(Redwood)myTreeVariable
没有区别。
答案 2 :(得分:3)
我在我的解释中又添加了一个子类。
Tree
/ \
/ \
/ \
Redwood Blackwood
在此层次结构中:
<强>向上转型强>:
当我们沿着类层次结构在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
<强>低迷现状强>:
当我们沿着类层次结构在from the root class towards the children or subclasses
方向上转换引用时。 我们需要明确输入。
Downcast示例:
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
<强> [ANSWER] 强>
Redwood r = (Redwood) new Tree();
为什么没有编译错误?
Downcast 的例子:
source Redwood r = (Redwood) new Tree();
创建Tree对象并将其转换为Redwood
。
你可以这样想:
Tree t = new Tree();`
Redwood r = (Redwood)t;
所以它在编译时可以,
<强> [ANSWER] 强>
为什么运行时错误?
但真正的Redwood
子类不能指向Tree
超类对象。所以你的代码在运行时失败了。
树t =新树();
t
指向Tree()
对象而不是Redwood()
。这是运行时错误的原因。
编译器现在不会在t
和语法中写出什么是值。但是在Redwood r = (Redwood)t;
的运行时,t
是Tree class
的对象,如果出错了。
[建议:]
我建议您使用 instanceof 运算符:
转换/强制操作用于在类型之间进行转换, instanceof 运算符用于在运行时检查类型信息。*
说明:
instanceof 运算符允许您确定对象的类型。它接受运算符左侧的对象和运算符右侧的类型,并返回布尔值,指示对象是否属于该类型。通过一个例子可以最清楚地证明这一点:
if (t instanceof Redwood)
{
Redwood r = (Redwood)t;
// rest of your code
}
但我还要补充一句:从基类型转换为派生类型是一件坏事。 参考:Beware of instanceof operator
答案 3 :(得分:1)
我怀疑编译器何时会在编译器级别发出转换异常以及它何时会在运行时发生?
严格地说,编译器不会“发出转换异常”。你会得到:
(Redwood) new Tree()
给出运行时异常而不是编译错误的原因是JLS (section 5.5.1)所说的应该发生的事情。
具体来说,JLS说:
“给定编译时引用类型S(源)和编译时引用类型T(目标),如果由于以下规则没有发生编译时错误,则从S到T存在转换转换。“
“如果S是类类型” AND “如果T是类类型,那么| S |&lt ;: | T |或| T |&lt;:| S |。否则,发生编译时错误。“
“| S |&lt ;: | T |”表示类型S是T型或T的子类型。
在这种情况下,S是Tree
,T是Redwood
,两者都是类,Redwood
是Tree
的子类型...所以没有编译错误。
显然这是“错误的”这一事实并不重要。 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;
假设他们将第一个片段定义为编译错误:
第二个怎么样?那也很容易证明。
第三个怎么样?这可能很容易......或者可能非常困难。
第四个怎么样?现在片段的合法性取决于我们甚至可能没有源代码的方法的语义!
关键是,一旦你开始要求编译器证明关于表达式的动态值的事情,你就会陷入滑坡,导致Halting问题。如果你使编译错误成为可选的,那么你会得到一个可怕的情况:一个编译器可以说程序是有效的,另一个可以说它有错误!
答案 4 :(得分:0)
有必要补充说,不需要向下转换:由于Redwood也是一个树,你总是可以将它分配给一个树变量,
Redwood r;
Tree t = r; // no casting needed
您可能始终在Java类型系统中查找替换原则。我确信这些材料都是千兆吨。
答案 5 :(得分:0)
引用的Tree可能事先是Redwood,这就是代码编译得很好的原因。
Tree t1 = new Redwood();
Redwood r1 = (Redwood)t1;
Tree t2 = (Tree)r1;
你可以看到树()可能已经是红木了。