【问题标题】:What are some hardfast "rules" for casting with subtypes?使用子类型进行强制转换有哪些严格的“规则”?
【发布时间】:2017-06-10 09:48:44
【问题描述】:

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

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

【问题讨论】:

  • 如果您对选角有疑问。您可以在继续投射之前使用instanceof
  • a的具体类型是什么?是C吧? C也是B吗?是的,因为 C 扩展了 B。所以演员表会成功。用真正的类来推理要简单得多。假设A是Vehicle,B是Car,C是ElectricCar。电动汽车是汽车。汽车是交通工具。 a 所指的对象是 ElectricCar。说这个对象是汽车是否正确?是的。所以演员表成功了。
  • @Enzokie 这对现实世界来说是个好建议,但遗憾的是,如果出现此类问题,它在考试期间对我没有帮助:/
  • @JBNizet 这是一个很好的思考方式!您提出了一个很好的观点,即我应该在精神上将这些名称不佳的类视为更真实的名称。这绝对可以帮助我。谢谢!

标签: java casting subtyping


【解决方案1】:

当层次结构复杂时,试着把它画出来,这样更清晰:

A <- B <- C

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

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

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

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

(X)Y

其中X 是引用类型,Y 是引用类型的变量,如果您可以在继承层次结构中从Y 的基础类型转到X,则转换将在运行时成功只能沿着箭头方向前进

假设我们有这个代码:

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

这将失败,因为我们需要逆箭头方向从A 转到B


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

这很容易。只需检查Y变量类型(不是底层类型!)是否与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

【讨论】:

  • 请将X(Y)中的第一个Y改成y,因为它是一个变量名。还是我错过了什么?
  • 这只是一个占位符,用于表示该位置发生的任何事情。 Y 本身不是变量@KedarMhaswade
  • 这令人困惑。您在上面说“Y 是引用类型的变量”。
【解决方案2】:

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

一些术语和简化是为了:

  • 当您说“将一个类型转换为另一个类型”时,前者称为source类型,后者称为目标类型。现在让我们将讨论限制在具体的引用类型(即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| &lt;: |T|, or |T| &lt;: |S|。否则,会发生编译时错误。|S| 表示 S擦除&lt;: 表示 is-subclass-of 关系。)

现在,ABC 类创建以下层次结构:C &lt;: B &lt;: 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)的类型BA(即Source)的子类,所以(2)中的赋值应该可以正常编译,因为您将 from 转换为 is-subclass 中的源类 (A) to 目标类 (B) -of 关系。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-23
    • 2020-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多