我已经实现了以下代码,以便理解静态和动态绑定之间的区别:
class A {
int met(A a) {
return 0;
}
int met(B b) {
return 1;
}
int met(C c) {
return 2;
}
}
class B extends A {
int met(A a) {
return 3;
}
int met(B b) {
return 4;
}
int met(C c) {
return 5;
}
}
class C extends B {
int f() {
return ((A)this).met((A)this);
}
}
public class Test {
public static void main(String[] args) {
C x = new C();
System.out.println(x.f());
}
}
我得到的结果是3,但我不明白为什么,因为第一次演员是为了A。
答案 0 :(得分:6)
所以,让我们来看看电话:
((A)this).met((A)this);
相当于:
A target = this;
A argument = this;
target.met(argument);
因此,对于最后一行,编译器根据所涉及的编译时类型查找签名 - 它将在A
(和超类)中查找名为{的方法{1}},其参数与met
(参数的编译时类型)兼容。重载决议发现答案是:
A
在编译时确定了签名。但是,该方法的实现是在执行时根据方法调用的执行时间目标确定的。该类型在此int met(A a)
- 因为C
是对this
实例的引用。 (在C
的实例上调用f
方法。)
现在C
不会覆盖C
,但是int met(A a)
(它的超类)会这样做 - 这就是所使用的实现。 B
还覆盖B
和met(B b)
并不重要,因为编译器已经确定它是met(C c)
正在调用的方法。
答案 1 :(得分:2)
这是一个价格的两个问题。 (1)为什么我们从B类获得实现,以及(2)为什么我们得到带有A类参数的方法版本。
对于问题(1),要记住的是,对象的类在投射时不会改变,或者将其分配给类型不同的变量。因此,在您的示例中,this
的类始终为C
,因为这是您创建的内容。类C
从类met(A a)
继承其B
版本,因为它没有自己的版本,并且因为类B
已覆盖类{{}中的版本1}}。这就是多态性的全部内容 - 方法的版本取决于您调用它的对象的类,而不是您使用的表达式的类型叫它。
对于问题(2),Java的一个小问题是在编译时评估方法签名。因此编译器会发现您已将 type A
的表达式传递给您的方法,因此它会选择签名A
。因为这个决定是在编译时做出的,所以参数的实际类没有任何区别 - 编译器已经根据表达式的类型选择了该方法。换句话说,Java 不会为您提供方法参数的多态性。