Java为什么能运行期间动态确定调用方法?是怎么实现的呢。
在继承关系中,运行时动态确定要调用的方法的版本,是根据字节码指令invokevirtual来完成的。需要从这个指令的"多态查找"过程说起。
invokevirtual的运行过程如下:
第一、找到操作数栈顶元素的实际类型,记做C,
第二、如果在才、C中找到与常量中描述符和简单名称都相同的方法,则进行权限校验,如果通过,则返回这个方法的直接引用,查找过程结束。权限校验不通过则返回java的illegalaccesserror。
第三、如果经过第二步没找到也没抛出异常,则按照继承关系在父类中查找。
第四、如果始终没有找到,则跑出abstractmethoderror。
由于invokevirtual指令在执行的第一步就是确定接收者的实际类型,所以调用的时候,针对不同的调用者(即所说的方法接收者)会把常量池中的类方法解析到不同的直接引用上,这个过程就是java语言“覆盖”父类方法的本质,也就是“重写”的本质。
父类 a=new 子类1
父类 b=new 子类2
a.test();
b.test();
上面代码a和b分别会调用两个子类的test函数,可以作为例子参考。
-------------------------------------------------------------------------------------------------------
上面的全部都是在说子类重写父类的方法,那么如果在同一个类中重载一个函数,那么是怎么确定调用版本的呢?
这需要从“静态类型”和“动态类型”说起。
比如:
父类 a=new 子类,
在上面,父类是静态类型,子类是动态类型,【因为子类是这个对象的实际类型,称之为动态类型】
如果在一个类
class TR {
void test(父类 a) {
System.out.print("father");
}
void test(子类1 a) {
System.out.print("child 1");
}
void test(子类2 a) {
System.out.print("child 2");
}
}
代码如上。调用的时候,如果这么调用:
父类 a=new 父类
父类 b=new 子类1
父类 c=new 子类2
new TR().test(a);
new TR().test(b);
new TR().test(c);
那么这三句话会分别输出什么呢?
这个有可能会有人回答错误。
会输出三个father!
这是因为,虚拟机(准确的说是编译器)在重载时候是通过静态类型而不是动态类型来确定调用方法的版本的。所以三个调用语句都会选择void test(父类 a)作为调用函数。