1

我正在为即将到来的 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 级更深。演讲幻灯片并没有这种信息!

那么,如果考试中出现此类问题,需要牢记哪些严格的“规则”?

4

2 回答 2

2

当层次结构复杂时,尝试将其绘制出来以使其更清晰:

A <- B <- C

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

a的基础类型 C. 但是,C可转换为B and A因为C继承自BB继承自A

基本上,关于引用类型转换是否成功的一般规则如下:

对于以下格式的任何演员表:

(X)Y

whereX是一个引用类型并且Y是一个引用类型的变量,如果你只能沿着箭头的方向Y从的底层类型进入X继承层次结构中,则转换将在运行时成功。

假设我们有这个代码:

A a = new A();
B b = (B)a;

这将失败,因为我们需要逆箭头的方向从AB


那么,您如何知道强制转换是否会在编译时失败?

这很容易。只需检查Y's变量类型(不是底层类型!)是否与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
于 2017-06-10T11:21:04.757 回答
1

要完全理解这个看似简单的问题所涉及的问题需要很长时间,并且需要理解Java语言规范,但体面的理解或许是触手可及的。@JBNizet 的具体化想法也很有用。

一些术语和简化是为了:

  • 当您说“一种类型转换另一种类型”时,前者称为类型,后者称为目标类型。现在让我们将讨论限制在具体的引用类型(即类,如ABC)。我们将源类型表示为Source,将目标类型表示为Target
  • 在涉及引用类型转换(隐式或显式)的赋值语句中,源类型的变量出现在符号的右侧=,目标类型的变量出现在其左侧。因此,您可以:Target t = (Target) s,其中s是 类型的变量Source
  • 引用类型的变量有两种类型:编译时类型和运行时类型。

现在,根据 JLS(经过一些简化)的适用规则是:

Target t = (Target) s要编译这种赋值 ( ),要么Target必须是 的子类(或子类型)Source,要么Source必须是 的子类Target

(实际的、严格的规则是:If T is a class type, then either |S| <: |T|, or |T| <: |S|。否则,会发生编译时错误。|S|暗示删除S<:暗示is-subclass-of关系。)

现在,您的类ABC创建以下层次结构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) 

现在,按照上面的适用规则,由于 (ie ) 的类型是b(ie )TargetB子类,因此 (2) 中的赋值应该可以正常编译,因为您正在源类 ( )转换为目标类( ) 处于is-subclass-of关系中。ASourceAB

根据(1),变量的运行时类型a实际上是。C并且由于C是 的子类B,任何类型C的变量(源)总是可以分配类型的变量B(目标),以便在运行时成功进行强制转换(即它抛出ClassCastException)。这称为里氏替换原则

于 2017-06-10T17:10:39.467 回答