我正在为我的Java决赛进行一些练习考试,我遇到了这个问题。
考虑以下类定义,并指出'Test.main()'是否可以成功编译。如果它 编译,指示它是否会成功运行,或者如果没有,则指出将抛出什么异常。
public class A {
public int method(int[] a) {...}
}
public class B extends A {
@Override
public int method(int[] a) {...}
}
public class C extends B {
@Override
public int method(int[] a) {...}
public void otherMethod() {...}
}
public class Test {
public static void main(String[] args) {
A a = new C();
B b = new B();
b = (B) a;
}
}
我认为Test.main()会编译但是会抛出一个运行时异常,因为a是实际类型C并且我们正在尝试将它强制转换为类型B.事实并非如此,因为答案说这个很好。
我对铸造规则感到非常困惑,其中层次结构的深度超过了2级。讲座幻灯片并没有这种信息!
那么,如果考试中出现这类问题,请记住哪些难以解决的“规则”?
答案 0 :(得分:2)
当存在复杂的层次结构时,请尝试将其绘制出来以便更清楚:
A <- B <- C
我认为Test.main()会编译但是抛出一个运行时异常,因为a是实际的C类,我们正试图将它强制转换为B类。
a
的基础类型是 C
。但是,C
可转换为B
和 A
,因为C
继承自B
而B
继承自A
{1}}。
基本上,关于引用类型转换是否成功的一般规则如下:
对于以下格式的演员:
(X)Y
其中
X
是引用类型而Y
是引用类型的变量,如果您可以从Y
的基础类型转换,则转换将在运行时成功继承层次结构中的X
仅沿箭头方向。
说我们有这个代码:
A a = new A();
B b = (B)a;
这会失败,因为我们需要违背箭头的方向从A
转到B
你怎么知道演员阵容是否会在编译时失败呢?
这很容易。只需检查Y
&#39> 变量类型(非基础类型!)是否与X
无关。
例如:
// these two types are unrelated
class Foo {}
class Bar {}
// ...
Foo f = new Foo();
Bar b = (Bar)f; // fails to compile
但是,如果Y
的变量类型与X
相关,则编译正常:
Object f = new Foo();
Bar b = (Bar)f; // Bar inherits from Object, so compiles fine.
// But since Foo (f's underlying type) is unrelated to Bar
// this crashes at runtime
答案 1 :(得分:1)
完全理解这个看似简单的问题所涉及的问题需要花费很长时间才能理解the Java language specification,但是一个体面的理解可能是在一个人的范围内。 @ JBNizet的具体化思路也很有用。
一些术语和简化是有序的:
A
,B
和C
等类)。我们将源类型表示为Source
,将目标类型表示为Target
。 =
符号的右侧,而目标类型的变量则显示在其左侧。因此,您执行:Target t = (Target) s
,其中s
是Source
类型的变量。现在,适用的规则according to the JLS(经过一些简化)是:
对于要编译的这种赋值(
Target t = (Target) s
),Target
必须是Source
的子类(或子类型),或者Source
必须是子类Target
。
(实际严格的规则是:If T is a class type, then either |S| <: |T|, or |T| <: |S|
。否则,会发生编译时错误。|S|
表示S
和{{{em> erasure 1}}表示 is-subclass-of 的关系。)
现在,您的课程<:
,A
和B
会创建以下层次结构:C
:
C <: B <: A
方法可以有效地表达为:
main
现在,按照上面适用的规则进行操作,因为A a = new C(); //as-is (1)
// the intervening B b = new B() does not make any difference
B b = (B) a; //as-is (2)
(即b
)的类型Target
是B
的子类(即{{1}在(2)中的赋值应该编译正常,因为你将从源类(A
)转换为目标类({{ 1}})在 is-subclass-of 关系中。
根据(1),变量Source
的运行时类型实际上是A
。由于B
是a
的子类,因此任何类型C
的变量(源)始终可以分配给类型{{1}的变量(转换)在运行时成功(即,不抛出C
)。这称为Liskov Substitution Principle。