有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java使用子类型强制转换的一些硬性“规则”是什么?

我在为即将到来的Java期末考试准备一些练习考试时,遇到了这个问题

Consider the following class definitions and indicate whether 'Test.main()' would compile successfully. If it does compile, indicate whether it would run successfully or if not, indicate what Exception would be thrown.

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;
    }
}

我以为是考试。main()会编译,但会抛出一个运行时异常,因为a是实际类型C,我们正试图将其转换为类型B。情况并非如此,因为答案说这是可以的

我对演员的规则非常困惑,因为其中涉及的层次比两个级别更深。课堂幻灯片上并没有这种信息

那么,如果这类问题在考试中突然出现,有哪些硬性的“规则”需要牢记


共 (2) 个答案

  1. # 1 楼答案

    要完全理解这个看似简单的问题所涉及的问题需要很长时间,而且需要一个人理解the Java language specification,但一个像样的理解也许就在眼前@JBT的思想具体化也很有用

    一些术语和简化如下:

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

    现在,适用的规则according to the JLS(经过一些简化后)是:

    For this kind of assignment (Target t = (Target) s) to compile, either Target must be a subclass (or subtype) of Source, or Source must be a subclass of Target.

    (实际的严格规则是:If T is a class type, then either |S| <: |T|, or |T| <: |S|。否则,会发生编译时错误。|S|表示S擦除,而<:表示关系的子类。)

    现在,您的类ABC创建以下层次结构:C <: B <: A

    class hierarchy

    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的一个子类A(即Source),所以(2)中的赋值应该编译得很好,因为您正在将源类(A转换为目标类(B),该类位于关系的子类中

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

  2. # 2 楼答案

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

    A <- B <- C
    

    I thought that Test.main() would compile but throw a runtime exception due to the fact that a is of actual type C and we are trying to cast it to type B.

    a的底层类型C。然而,C可转换为B{},因为C继承自B,而B继承自A

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

    For any cast in the below format:

    (X)Y

    where X is a reference type and Y is a variable of a reference type, the cast will succeed at runtime if you can go from Y's underlying type to X in the inheritance hierarchy by only going along the directions of the arrows.

    假设我们有这个代码:

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

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


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

    这很容易。只需检查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