Java 中的方法绑定

Method Binding in Java

最近在看Thinking in Java(第4版)时,遇到了一个关于Java中方法绑定的问题。首先让我们看一下书中的两个定义:

  1. 将方法调用连接到方法体称为绑定。
  2. 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()

根据这个结果,我有以下问题:

  1. 根据书上的定义,由于我所有的方法都是可继承的,所以Java将对所有三个语句使用后期绑定(动态绑定)。但是,我读了一些其他文章,其中说 Java 在处理重载时使用静态绑定。这似乎是矛盾的,因为显然陈述 1 是重载。
  2. 我不太明白为什么Java在语句2中调用了baseclass的ovrLd()。如果使用动态绑定,应该调用subclass的overLd()因为到运行时 JVM 应该清楚 sub2 是 BindingTest_Sub class 的一个实例。另一方面,如果它使用静态绑定,它还应该调用 sub class 的 overLd(),因为编译器能够观察到给定参数的类型是 Integer。你能告诉我编译器或JVM在处理语句2时做了什么工作吗
  3. 陈述 3 的结果对我来说很有意义。但是,我仍然很好奇编译器如何将它 (ovrRd()) 识别为覆盖方法。换句话说,编译器如何知道另一个 class 有一个方法覆盖了这个 ovrRd()。

对上述问题或 Java 方法绑定机制的任何想法表示赞赏。也请随时指出我的错误。

TL;DR; 你并没有真正重载 ovrLd(Object),不是在运行时。编译器使用编译时类型信息来决定调用哪个虚方法是最好的。 sub1sub2 有不同的编译时类型。 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 ) 与方法的实际内存地址相关联(具体实现)。因为这些方法不能被其他人覆盖甚至访问,所以它们的实现不能动态改变。除了那些方法,其他方法在运行时绑定具体实现。