为什么类能够仅在运行时错误而不是编译器错误的情况下强制转换不相关的类

时间:2018-12-12 19:16:10

标签: java casting compiler-errors polymorphism

这是我的代码:

public class A {
    int size = 100;
    public int getSize() {
        return size;
    }
    interface D {

    }
    class B implements D {

    }
    class C {
        int size = 999;
        public int getSize() {
            return size;
        }
    }
    public void test() {
        D d = new B();
        System.out.println(((C) d).getSize());
    }
    public static void main(String[] args) {
        A a = new A();
        a.test();
    }
}

代码编译时没有任何编译器错误。为什么没有编译器错误。 C类与引用类型D和实际类B没有任何关系。它如何通过编译器的类型转换检查?

2 个答案:

答案 0 :(得分:3)

从JLS 5.5.1,引用类型转换: https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.5.1

  

给定编译时参考类型S(源)和编译时参考类型T(目标),如果由于以下规则而没有发生编译时错误,则存在从S到T的转换转换。

在我们的例子中,S = D(接口),T = C(类)。

  

如果S是类类型:

S是一个接口,所以我们跳过它。

  

如果S是接口类型:

D是一个接口,因此我们使用此分支。

  

如果T是数组类型,则S必须是java.io.Serializable或Cloneable类型(数组唯一实现的接口),否则会发生编译时错误。

T不是数组类型,我们跳过它。

  

如果T是不是最终类型的类或接口类型(第8.1.1节),则如果存在T的超类型X和S的超类型Y,则X和Y都是可证明是不同的参数化类型,并且X和Y的擦除相同,则发生编译时错误。

我们没有这个。我们当然可以创建一个导致这种情况发生的方法,但这需要重新定义C才能适合此模型。

  

否则,强制转换在编译时始终是合法的(因为即使T不实现S,T的子类也可能)。

换句话说,编译器没有理由抛出错误,因为很明显在其他地方可能存在可以用于限定类型转换的类。

例如,您可以在其他地方使用它:

class E extends C implements D { ... }

最终确定C会导致编译时错误:

final class C { ... }

基本上,存在一种可能存在的C实现,它可以在运行时运行,而无法在编译时进行验证。

答案 1 :(得分:2)

这是我的人工解释,有关规范的详细信息,请参见@Compass答案。

原因是因为您是从一个接口进行转换的,而该接口中所保存的实例的实际类可能与您要转换的类相匹配。 d可以容纳C类型的实例。

只有变量类型对编译时很重要,而实例的实际类型对运行时很重要。

有关示例,请参见以下代码:

class B { ... }
class C { ... }
interface D { ... }

// only variable type matters for compile-time
// the compiler (usually?) doesn't care what's in them
D d = ...;
B b = ...;

// compile error
// `b` can't contain an instance inheriting from `C`.
C c2 = (C) b;

// no compile error
// `d` might contain an instance inheriting from `C`.
C c1 = (C) d;


// it's because of the possibility like below
// that the compiler doesn't complain.
class E extends C implements D { ... }

D d = new E();
C c = (C) d;