运行时多态性决策

时间:2015-11-03 12:56:28

标签: java jvm polymorphism compiler-optimization

我在某处读过运行时多态性是langugages中动态类型的结果。通过检查下面的代码,我们可以看到运行时多态性的一个明确的例子。

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();

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

3 个答案:

答案 0 :(得分:4)

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

但是由于方法调用的虚拟化是一个重要的优化(保存vtable查找,可能内联方法),运行时(热点等)将尝试应用它,如果可能的话,由于代码分析

因此,在您的第二个示例(第三个代码块)中,运行时可能会识别它可以通过调用A.do来替换B.do的虚拟调用,因为{{1 实际上是ex(在这种情况下运行时应该很容易弄明白)。

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

答案 1 :(得分:3)

首先,“动态打字”一词,在你的问题中使用它的方式,充其量是误导。 Java 不是“动态类型编程语言”。它提供了某些动态类型检查,如类型转换和instanceof运算符,但在您的代码示例中,不涉及动态类型检查。这些都是静态输入的。

顺便说一句,do不是Java中的合法方法名称。但是假设,A声明方法doSomething并且有一个子类B,那么它与Java编译器完全无关,B是否覆盖doSomething (除非它改变了访问修饰符)。

关键是,AB是不同的类,可以独立进行(重新)编译,并且B仍然没有(不)覆盖该方法的保证运行。但是规范认为这种变化属于不应破坏二进制兼容性的合法范围:

  

13.4.24. Method Overriding

     

如果将实例方法添加到子类并覆盖超​​类中的方法,则子类方法将通过预先存在的二进制文件中的方法调用找到,并且这些二进制文件不会受到影响。

     

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

注意关于“类方法”的最后一句,即static方法。这意味着当A声明static方法m并通过B.m调用它时,调用可能会以static方式声明B方法1}}如果B碰巧在运行时声明这样的方法,即使在编译时看到的版本没有。因此,即使是早期绑定的非多态方法最终也会在运行时解析,并且可能找到与编译时不同的目标。后期绑定方法的不同之处在于,一旦解析了早期绑定方法,就会始终将调用分派给方法,而不依赖于可能在运行时更改的任何属性。

对于可覆盖的方法,该方法根据调用它的引用的编译时类型来解析,然后然后在引用的实际运行时类型中可能存在重写方法。这是一个向前回答潜在后续问题的地方:

  

13.4.17. final Methods

     

将声明为final的方法更改为不再声明final不会破坏与预先存在的二进制文件的兼容性。

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

唯一获得特殊处理的方法调用是调用private方法。由于private方法的调用者总是与方法声明本身在同一个类中,因此它们总是被编译在一起而不受独立进化的影响。

答案 2 :(得分:0)

编译器是否在编译时绑定或者JVM是否在运行时绑定是故意未定义的。 java规范不会以这种或那种方式陈述 - 它只会说明在运行代码时,预期的结果将会实现[Citation Needed]。

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