【问题标题】:Static/dynamic binding and static/dynamic dispatch are a bit confusing静态/动态绑定和静态/动态调度有点混乱
【发布时间】:2017-09-07 09:49:08
【问题描述】:

我想澄清一些关于绑定和调度的事情。

绑定是将方法调用附加到方法签名。 Dispatch 正在选择方法实现。首先是绑定,然后是调度。好的,我知道了,但是:

  1. 没有被覆盖的实例方法是否使用静态绑定?
  2. 为什么重载方法使用静态绑定,因为它们是虚拟的并且可以在子类中被覆盖?

过去几天我读了很多解释,现在对我来说一团糟。其中一些是相互冲突的或完全相反的。据我所知,Java 中所有非static、非private 和非final 方法默认都是虚拟的,必须使用动态绑定和动态调度。

【问题讨论】:

  • 1.不,因为它可能会被其他人覆盖。编译器不会知道方法没有被覆盖。异常是私有方法和最终方法,至少前者是静态绑定的。 2. 是什么让你觉得“重载方法使用静态绑定”?

标签: java


【解决方案1】:

绑定是将方法调用附加到方法签名。 Dispatch 正在选择方法实现。

您所说的绑定是指重载。您所说的调度是指覆盖。

首先是绑定,然后是调度。

是的。编译器进行绑定,JVM 进行调度。

好的,我知道了,但是:

  1. 没有被覆盖的实例方法是否使用静态绑定?

没有。编译器无法判断,除了private 方法,在这种情况下它使用invokespecial 字节码。

  1. 为什么重载方法使用静态绑定,因为它们是虚拟的并且可以在子类中被覆盖?

他们没有。

据我所知,Java 中的所有非静态、非私有和非最终方法默认情况下都是虚拟的,并且必须使用动态绑定和动态调度。

正确。

【讨论】:

  • 现在我了解重载方法是虚拟的,在运行时使用动态绑定,但是:3) 重载方法使用静态绑定绑定,而重写方法在运行时使用动态绑定绑定。这是什么意思?
  • @violet_eyes 我见过这样的说法“重载的方法是使用静态绑定绑定的,而重写的方法是在运行时使用动态绑定绑定的。”在少数博客上,但这种说法并不总是正确的。这完全取决于调用和声明方法的方式。您可以看到我在回答中提供的情况,其中重载方法是动态绑定的。
  • 这部分好像错了:没有。编译器无法判断这一点,除非是私有或最终方法。 只有一个字节码:invokevirtual 在编译时调用非静态、非接口方法,即使是 private/final方法,因此编译时行为是相同的。在运行时编译 (JIT) 期间,final 和 private 也无助于任何现代 JVM 进行 类层次结构分析 以确定是否存在任何覆盖方法,无论是否存在 @987654327 @ 或 private。所以在实践中,这些限定符不会改变编译或运行时的行为。
  • @EJP - 另请参阅下面我对拉曼的 cmets:即使对于 final 和 private 方法,编译时绑定也是动态的:您只有 invokevirtual 字节码。根本没有办法在.class 文件中表达“调用该类中实现的此方法,并且从不子类中的任何覆盖方法”。所以即使编译器可以猜到一个方法不能被覆盖,它仍然会进行动态绑定(如下所示,猜测可能是错误的,因为类可以改变)。
  • @BeeOnRope invokevirtual 在 private 或 final 方法的情况下不会发出。还有其他字节码可供调用。
【解决方案2】:

java中的绑定规则:

1) 对静态方法的调用是静态绑定的。

2) 对构造函数的调用是静态绑定的。

3) 对非静态方法的调用是动态绑定的:

例外情况: 3.a) 对私有的非静态方法的调用是静态绑定的。

3.b) 在子类中使用super 调用的非私有、非静态方法也是静态绑定的。

那么对于第一个问题,即没有被覆盖的实例方法是否使用静态绑定?

答案是否定的。 如果该方法尚未被覆盖,但它可以(将来)。由于它是一个实例方法,因此可以使用实例(动态绑定)调用或使用超级(静态绑定)调用。

第二个问题:为什么重载的方法使用静态绑定,因为它们是虚拟的并且可以在子类中被覆盖?

答案:重载的方法不存在静态绑定。

说明:

class Sample{

    public void method1(){
        System.out.println("hello from A");
    }

    public void method1(String user){
        System.out.println("hello "+user+" from overloaded method");
    }

        public static void main(String []argh){ 
        Sample s = new Sample();
        s.method1();
        s.method1("name");
    }
}

在上面的代码中,方法method1()被重载了,但由于它既不是static,final也不是private,所以它生成invokevirtual指令,这意味着它是动态绑定的不是静态的。

要清除绑定和调度的区别,请参考Question

【讨论】:

  • final 方法在编译时不是静态绑定的。试试这个experiment。将Base、Derived 和Print 放入自己的文件中,然后将Base 和Print 与javac 一起编译。现在Print.printBase 应该将调用静态绑定到Base.print(),因为该调用是final。将Print.class 移到某处,现在从Base.print() 中删除final 修饰符。编译Derived(这也将编译Base和Print,因为javac编译依赖项。
  • 最后,将你最初编译的Print.class的“静态绑定”版本复制回来(替换最新的)。在Derived 中运行main()。请注意,它打印"derived"。尽管打印是针对final Base.print() 编译的,但没有静态绑定。您可以通过javap 确认这一点——生成的字节码使用invokevirtual 调用print()无论调用的方法是否是最终的。其实这是唯一字节码用来调用非静态、非接口方法,所以编译时不可能有静态绑定这种东西。
  • 之前我没有在我的答案中写final,但后来有些书让我感到困惑。在 Java 中,Complete 参考中提到“由于最终方法不能被覆盖,因此可以在编译时解决对方法的调用。这称为早期绑定”,在“Java 思维”中也有“所有方法绑定”在 Java 中使用后期绑定,除非该方法是静态或最终的”感谢您明确指出绑定不依赖于 final。
  • 好吧,我不确定那些书在说什么,但在 JVM 规范中 没有这样的机制 用于 static 普通绑定方法调用。从字面上看,方法调用只有一条指令:invokevirtual,所以只有一种绑定类型,即动态绑定。它不是特定于 final - 你可以用 private 做同样的事情,虽然它更难,因为如果你尝试覆盖 private 方法,验证将失败,但事实仍然是你可以编译一些调用private 方法,并且在运行时它可能会调用子类中的实现!
猜你喜欢
  • 1970-01-01
  • 2012-10-29
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多