运行时多态性决策

8

我曾经在某处看到说,运行时多态是动态类型语言的结果。通过检查下面的代码,我们可以看到一个明显的运行时多态的例子。

class A{
    do(){}
}
class B extends A{
    do(){}
}
...
A ex = new B();
ex.do();

由于存在超类类型引用,编译器无法确定引用将实际引用哪种类型并在运行时绑定方法。但是对于下面使用相同类定义的情况呢?

我的第一个问题是针对下面的示例;

class A{
    do(){}
}
class B extends A{
  //no overriding
}
...
A ex = new B();
ex.do();

在继承关系中只有一个do()方法的版本。系统是否仍然需要在运行时绑定该方法?还是在编译时绑定?

我的第二个问题是关于下面的示例的:

class A{
    do(){}
}
class B extends A{
  do(){}
}
...
B ex = new B();
ex.do();

现在有一个子类(继承链中最低的)类型的引用。它会在运行时绑定吗?


在编译时很难确定有多少实现可用,因为某人可以在运行时添加自己的实现。但是在运行时,JIT可以推测层次结构并执行以下操作。https://dev59.com/XZDea4cB1Zd3GeqPbGrw#33351238 - Ivan Mamontov
3个回答

5

在编译调用非静态方法javac时,将始终使用invokevirtual指令,因此在编译时没有进行优化。

但是,由于去虚拟化方法调用是一种重要的优化(节省vtable查找,可能内联方法),运行时(hotspot等)将尝试应用它,如果由于代码分析而可能实现。

因此,在您的第二个示例(第三个代码块)中,运行时可能会认识到它可以将对A.do的虚拟调用替换为对B.do的调用,因为ex实际上是一个B(在这种情况下,运行时应该很容易确定)。

对于您的第一个示例(第二个代码块),还有另一种优化技术。运行时首先看到类A。所有对A.do的调用现在都被编译为静态调用,就好像不存在覆盖A.do的派生类。如果稍后加载这样的类,则运行时将回滚此乐观假设,并引入虚拟方法调用。


第三个例子有什么见解吗? - Erick G. Hagstrom
1
@ErickG.Hagstrom澄清了引用:“第二个例子”=问题中的第三个代码块。 - wero

4
首先,你所提到的“动态类型”一词在Java中并不准确。Java并不是一种“动态类型编程语言”。它提供了某些动态类型检查,例如类型转换和instanceof运算符,但在你的代码示例中,并没有涉及任何动态类型检查。所有类型都是静态的。
顺便说一句,在Java中do不是合法的方法名。但是假设A声明了一个doSomething方法,并且有一个子类B,那么对于Java编译器来说,B是否覆盖doSomething是完全无关紧要的(除非它改变了访问修饰符)。
重点是,AB是不同的类,可以独立地进行(重新)编译,并且不能保证B在运行时仍然(不)覆盖该方法。但是规范认为这种变化在法律范围内,不会破坏二进制兼容性。

13.4.24. 方法重写

如果在子类中添加了一个实例方法并覆盖了超类中的方法,则现有二进制文件中的方法调用将找到子类方法,并且这些二进制文件不会受到影响。

如果向类中添加了一个类方法,则除非引用的限定类型是子类类型,否则将无法找到此方法。

请注意最后一句话提到的“类方法”,也就是static方法。这意味着当A声明一个static方法m,并通过B.m调用它时,如果在运行时B恰好声明了这样的方法,则调用可能会最终进入由B声明的static方法,即使在编译时看到的版本没有这样做。因此,即使是早期绑定的非多态方法最终也会在运行时解析,并且可能找到与编译时不同的目标。与晚期绑定方法的区别在于,一旦解析了早期绑定方法,调用总是分派给该方法,而不依赖于任何可能在运行时更改的属性。
对于可重写的方法,该方法根据调用它的引用的编译时类型进行解析,然后在引用的实际运行时类型中可能有一个覆盖方法。这是回答潜在后续问题的地方:

13.4.17. final Methods

将一个声明为final的方法更改为不再声明final,不会破坏与现有二进制文件的兼容性。

换句话说,在编译时调用一个final方法时,编译器不会利用目标方法是final的事实,因为该方法在运行时可能不是final,这种可能性不能破坏兼容性。

唯一得到特殊处理的方法调用是调用私有方法。由于私有方法的调用者始终在与方法声明本身相同的类中,它们总是一起编译,不受独立演化的影响。


抱歉我有点困惑。这个内容来自Grady Booch的OOAD书籍。下面的截图展示了来源。感谢您提供的信息。http://imgur.com/9iirXRj - Mehmet Çağrı Köse

0

编译器是否在编译时绑定,还是JVM在运行时绑定,故意不予定义。Java规范不会明确说明这一点 - 它只会说明在代码运行时将实现预期结果 [需要引用证明]。

早期/晚期绑定是一种优化,因此是可选的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接