方法在Java中绑定

时间:2015-04-01 01:18:28

标签: java jvm overloading override

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

  1. 将方法调用连接到方法主体称为绑定。
  2. Java中的所有方法绑定都使用后期绑定,除非该方法是静态的或最终的。
  3. 您可以在 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()
    

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

    1. 根据本书的定义,由于我的所有方法都是可继承的,因此Java将对所有三个语句使用后期绑定(动态绑定)。但是,我读到了其他一些文章,其中说Java在处理重载时使用静态绑定。这似乎是矛盾的,因为很明显声明1正在超载。
    2. 我不完全理解为什么Java在语句2中调用基类的ovrLd()。如果它使用动态绑定,它应该调用子类的overLd(),因为运行时JVM应该清楚sub2是一个实例BindingTest_Sub类。另一方面,如果它使用静态绑定,它也应该调用子类的overLd(),因为编译器能够观察到给定参数的类型是一个Integer。你能告诉我编译器或JVM在处理语句2时做了什么工作。
    3. 陈述3的结果对我有意义。但是,我仍然很好奇编译器如何识别它(ovrRd())作为一种重写方法。换句话说,编译器如何知道有另一个类具有覆盖此ovrRd()的方法。
    4. 对上述问题或Java方法绑定机制的任何想法都表示赞赏。另外,请随意指出我的错误。

2 个答案:

答案 0 :(得分:1)

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)

在这种情况下,它的工作方式如下:

编译器使用变量的编译时类型信息来决定调用哪个方法。 编译时类型sub2BindingTest_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中的所有方法绑定都使用后期绑定,除非该方法是静态的或最终的。

我认为这是真的,但含糊不清。歧义来自术语后期绑定。根据我的理解,这里的绑定意味着确定方法的特定实现,而不是方法的解决方案(解析为符号表中的符号) 。换句话说,编译器只是将一个方法引用一个符号,但该符号指向的内存中的位置是未确定的。

在课程加载时,静态方法和最终方法(私有方法隐式最终)与该方法的实际存储器地址相关联(具体实现)。由于这些方法无法被其他人覆盖甚至访问,因此无法动态更改其实现方式。除了这些方法之外,其他方法在运行时绑定到特定的实现。