Java 中的方法绑定
Method Binding in Java
最近在看Thinking in Java(第4版)时,遇到了一个关于Java中方法绑定的问题。首先让我们看一下书中的两个定义:
- 将方法调用连接到方法体称为绑定。
- Java 中的所有方法绑定都使用后期绑定,除非方法是静态的或最终的。
您可以在 多态性 章的 方法调用绑定 部分找到这些定义。 (第 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将对所有三个语句使用后期绑定(动态绑定)。但是,我读了一些其他文章,其中说 Java 在处理重载时使用静态绑定。这似乎是矛盾的,因为显然陈述 1 是重载。
- 我不太明白为什么Java在语句2中调用了baseclass的ovrLd()。如果使用动态绑定,应该调用subclass的overLd()因为到运行时 JVM 应该清楚 sub2 是 BindingTest_Sub class 的一个实例。另一方面,如果它使用静态绑定,它还应该调用 sub class 的 overLd(),因为编译器能够观察到给定参数的类型是 Integer。你能告诉我编译器或JVM在处理语句2时做了什么工作吗
- 陈述 3 的结果对我来说很有意义。但是,我仍然很好奇编译器如何将它 (ovrRd()) 识别为覆盖方法。换句话说,编译器如何知道另一个 class 有一个方法覆盖了这个 ovrRd()。
对上述问题或 Java 方法绑定机制的任何想法表示赞赏。也请随时指出我的错误。
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)
的虚拟调用
现在,虚拟调用的运行时方法是根据被调用方法的完整签名(名称 + 参数)决定的。 BindingTest_Sub
中的 ovrLd(Object)
没有重载
所以基本的class方法被调用了。
使用 sub1
编译器有更多信息。 sub1
的编译时类型是BintdingTest_Sub
,在那个class.
中有一个匹配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
经过一些研究,我想我理解 Thinking in Java 的作者试图表达的意思。
作者说 Java 中的所有方法绑定都使用后期绑定,除非方法是静态的或最终的。
我认为这是真的,但模棱两可。歧义来自术语 late binding。以我的理解,这里的binding是method的具体implement的确定,不是method的resolution (解析为符号 table 中的符号)。换句话说,编译器只是将一个方法引用到一个符号,但该符号在内存中指向的位置是不确定的。
在class加载点,static方法和final方法(private 方法隐式 final ) 与方法的实际内存地址相关联(具体实现)。因为这些方法不能被其他人覆盖甚至访问,所以它们的实现不能动态改变。除了那些方法,其他方法在运行时绑定具体实现。
最近在看Thinking in Java(第4版)时,遇到了一个关于Java中方法绑定的问题。首先让我们看一下书中的两个定义:
- 将方法调用连接到方法体称为绑定。
- Java 中的所有方法绑定都使用后期绑定,除非方法是静态的或最终的。
您可以在 多态性 章的 方法调用绑定 部分找到这些定义。 (第 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将对所有三个语句使用后期绑定(动态绑定)。但是,我读了一些其他文章,其中说 Java 在处理重载时使用静态绑定。这似乎是矛盾的,因为显然陈述 1 是重载。
- 我不太明白为什么Java在语句2中调用了baseclass的ovrLd()。如果使用动态绑定,应该调用subclass的overLd()因为到运行时 JVM 应该清楚 sub2 是 BindingTest_Sub class 的一个实例。另一方面,如果它使用静态绑定,它还应该调用 sub class 的 overLd(),因为编译器能够观察到给定参数的类型是 Integer。你能告诉我编译器或JVM在处理语句2时做了什么工作吗
- 陈述 3 的结果对我来说很有意义。但是,我仍然很好奇编译器如何将它 (ovrRd()) 识别为覆盖方法。换句话说,编译器如何知道另一个 class 有一个方法覆盖了这个 ovrRd()。
对上述问题或 Java 方法绑定机制的任何想法表示赞赏。也请随时指出我的错误。
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)
现在,虚拟调用的运行时方法是根据被调用方法的完整签名(名称 + 参数)决定的。 BindingTest_Sub
中的 ovrLd(Object)
没有重载
所以基本的class方法被调用了。
使用 sub1
编译器有更多信息。 sub1
的编译时类型是BintdingTest_Sub
,在那个class.
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
经过一些研究,我想我理解 Thinking in Java 的作者试图表达的意思。
作者说 Java 中的所有方法绑定都使用后期绑定,除非方法是静态的或最终的。
我认为这是真的,但模棱两可。歧义来自术语 late binding。以我的理解,这里的binding是method的具体implement的确定,不是method的resolution (解析为符号 table 中的符号)。换句话说,编译器只是将一个方法引用到一个符号,但该符号在内存中指向的位置是不确定的。
在class加载点,static方法和final方法(private 方法隐式 final ) 与方法的实际内存地址相关联(具体实现)。因为这些方法不能被其他人覆盖甚至访问,所以它们的实现不能动态改变。除了那些方法,其他方法在运行时绑定具体实现。