使用子类型进行铸造有哪些硬性“规则”?

时间:2017-06-10 09:48:44

标签: java casting subtyping

我正在为我的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级。讲座幻灯片并没有这种信息!

那么,如果考试中出现这类问题,请记住哪些难以解决的“规则”?

2 个答案:

答案 0 :(得分:2)

当存在复杂的层次结构时,请尝试将其绘制出来以便更清楚:

A <- B <- C
  

我认为Test.main()会编译但是抛出一个运行时异常,因为a是实际的C类,我们正试图将它强制转换为B类。

a的基础类型 C。但是,C可转换为B A,因为C继承自BB继承自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的具体化思路也很有用。

一些术语和简化是有序的:

  • 当您说&#34;将转换为类型另一种类型&#34;时,前者称为类型,后者是目标类型。我们暂时将讨论局限于具体的引用类型(即ABC等类)。我们将源类型表示为Source,将目标类型表示为Target
  • 在涉及转换(隐式或显式)引用类型的赋值语句中,源类型的变量显示在=符号的右侧,而目标类型的变量则显示在其左侧。因此,您执行:Target t = (Target) s,其中sSource类型的变量。
  • 引用类型的变量有两种类型:编译时类型和运行时类型。

现在,适用的规则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 的关系。)

现在,您的课程<:AB会创建以下层次结构:C

class hierarchy

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)的类型TargetB的子类(即{{1}在(2)中的赋值应该编译正常,因为你将源类(A转换为目标类({{ 1}})在 is-subclass-of 关系中。

根据(1),变量Source运行时类型实际上是A。由于Ba的子类,因此任何类型C的变量(源)始终可以分配给类型{{1}的变量(转换)在运行时成功(即,抛出C)。这称为Liskov Substitution Principle