最近我在阅读 Thinking in Java (第4版)时,我遇到了一个关于Java中方法绑定的问题。首先让我们看一下本书中的两个定义:
您可以在 Polymorphism 章节的 Method-call binding 部分找到这些定义。 (第281-282页)
为证明这一点,我写了以下代码:
public class Test3{
public static void main(String[] args) {
BindingTest_Sub sub1 = new BindingTest_Sub();
BindingTest_Base sub2 = new BindingTest_Sub();
sub1.ovrLd(new Integer(1)); // statement 1
sub2.ovrLd(new Integer(2)); // statement 2
sub2.ovrRd(); // statement 3
}
}
class BindingTest_Base {
void ovrLd(Object obj){
System.out.println("BindingTest_Base ovrLd()");
}
void ovrRd(){
System.out.println("BindingTest_Base ovrRd()");
}
}
class BindingTest_Sub extends BindingTest_Base{
void ovrLd(Integer i){
System.out.println("BindingTest_Sub ovrLd()");
}
void ovrRd(){
System.out.println("BindingTest_Sub ovrRd()");
}
}
执行结果是:
BindingTest_Sub ovrLd()
BindingTest_Base ovrLd()
BindingTest_Sub ovrRd()
根据这个结果,我有以下问题:
对上述问题或Java方法绑定机制的任何想法都表示赞赏。另外,请随意指出我的错误。
答案 0 :(得分:1)
TL; DR; 你并没有真正超载ovrLd(Object)
,而不是在运行时。编译器使用编译时类型信息来确定哪个是最佳的虚拟方法。 sub1
和sub2
具有不同的编译时类型。 sub1
的类型与ovrLb(Integer)
具有不同的最佳匹配。
解释。你想知道:
sub1.ovrLd(new Integer(1))
调用BindingTest_Sub.ovrLd(Integer)
sub2.ovrLd(new Integer(2))
正在调用BindingTest_Base.ovrLd(Object)
在这种情况下,它的工作方式如下:
编译器使用变量的编译时类型信息来决定调用哪个方法。
编译时类型sub2
为BindingTest_Base
,在运行时,您为其分配BindingTest_Sub
,但这与编译器无关。
BindingTest_Base中与该调用的参数匹配的唯一方法是:BindingTest_Base.ovrLd(Object)
因此,编译器会向orvLd(Object)
现在,虚拟调用的运行时方法是根据被调用方法的完整签名(名称+参数)决定的。 ovrLd(Object)
中BindingTest_Sub
没有重载
因此调用基类方法。
使用sub1
编译器可以获得更多信息。 sub1
的编译时类型为BintdingTest_Sub
,并且有一个方法与该类中的ovrLd(Integer)
匹配。
查看字节码,您可以清楚地看到:
aload 1 // sub1
// ... blah blah blah creating the integer
// the last opcode issued by the compiler for "statement 1"
INVOKEVIRTUAL com/ea/orbit/actors/samples/helloworld/BindingTest_Sub.ovrLd (Ljava/lang/Integer;)V
aload 2 // sub2
// ... blah blah blah creating the integer
// the last opcode issued by the compiler for "statement 2"
INVOKEVIRTUAL com/ea/orbit/actors/samples/helloworld/BindingTest_Base.ovrLd (Ljava/lang/Object;)V
答案 1 :(得分:1)
在做了一些研究之后,我想我明白了 Thinking in Java 的作者试图传达的内容。
作者说 Java中的所有方法绑定都使用后期绑定,除非该方法是静态的或最终的。
我认为这是真的,但含糊不清。歧义来自术语后期绑定。根据我的理解,这里的绑定意味着确定方法的特定实现,而不是方法的解决方案(解析为符号表中的符号) 。换句话说,编译器只是将一个方法引用一个符号,但该符号指向的内存中的位置是未确定的。
在课程加载时,静态方法和最终方法(私有方法隐式最终)与该方法的实际存储器地址相关联(具体实现)。由于这些方法无法被其他人覆盖甚至访问,因此无法动态更改其实现方式。除了这些方法之外,其他方法在运行时绑定到特定的实现。