这是我的代码:
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没有任何关系。它如何通过编译器的类型转换检查?
答案 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;