我在某处读过运行时多态性是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();
现在有子类(继承链中最低)类型引用。它会在运行时绑定吗?
答案 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
(除非它改变了访问修饰符)。
关键是,A
和B
是不同的类,可以独立进行(重新)编译,并且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]。
早期/晚期绑定是一种优化,因此是可选的。