最重要的区别是对象的静态和动态类型与对对象的引用。
假设B扩展A,C扩展B。
对象的动态类型(new 中使用的类型)是其实际的运行时类型:它定义了对象存在的实际方法。
对象引用(变量)的静态类型是编译时类型:它定义或更确切地说,声明变量引用的对象上可以调用哪些方法。
变量的静态类型应始终为与它所引用的对象的动态类型相同的类型或超类型。
因此,在我们的示例中,具有静态类型 A 的变量可以引用具有动态类型 A、B 和 C 的对象。具有静态类型 B 的变量可以引用具有动态类型 B 和 C 的对象。具有静态类型 C 的变量只能引用具有动态类型 C 的对象。
最后,在对象引用上调用方法是静态类型和动态类型之间微妙而复杂的交互。(如果您不相信我,请阅读有关方法调用的Java语言规范。
例如,如果 A 和 B 都实现了方法 f(),并且静态类型是 A,而涉及的动态类型是 C 用于方法调用,则将调用 B.f():
B extends A, C extends B
public A.f() {}
public B.f() {}
A x = new C(); // static type A, dynamic type C
x.f(); // B.f() invoked
简化得非常简单:首先,接收器(类型 A)和参数(无参数)的静态类型用于确定该特定调用的最佳匹配(最具体)方法签名,这是在编译时完成的。在这里,这显然是A.f()。
然后,在运行时的第二步中,动态类型用于定位方法签名的实际实现。我们从类型C开始,但我们找不到f()的实现,所以我们向上移动到B,在那里我们有一个与A.f()的签名匹配的方法B.f()。所以调用了 B.f()。
在我们的示例中,我们说方法 B.f() 覆盖方法 A.f()。在类型层次结构中重写方法的机制称为子类型多态性。