java中的方法调用-解析与分派

来源:互联网 发布:c语言游戏引擎 编辑:程序博客网 时间:2024/06/06 03:10

在本文开始之前,先搞清楚一个概念。对于People p = new Man();这句代码,我们把People叫做静态类型,Man叫做动态类型。静态类型在编译期可知,实际类型在运行期才可以确定下来。

解析

所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且运行期不可变。


java虚拟机中提供了5条方法调用字节码指令,分别如下:

invokestatic:调用静态方法

invokespecial:调用实力构造器<init>方法,私有方法和父类方法

invokevirtual:调用所有的虚方法

invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象

invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的四条调用指令,分派逻辑是固化在java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的


只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合这个条件的静态方法,私有方法,实例构造器,父类方法四类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去final方法)。final虽然使用invokevirtual调用,但是仍然是非虚方法。非虚方法是不可以被重写的。

解析调用一定是个静态的过程,在编译期间就完全确定。而分派调用可能是静态可能是动态的。


分派

静态分派

静态分派的典型应用是重载,重载根据参数的静态类型而不是实际类型作为判定依据。而静态类型在编译期可知,所以静态分派发生在编译阶段。

另外虽然编译器能确定方法的重载版本,但是在很多情况下重载版本不唯一,往往只能确定一个更加合适的版本。主要原因是字面量作为参数传入是没有显式的静态类型的。只能选择一个最贴近该字面型类型的方法。实际工作中要避免出现。

关于分派与解析的关系:并不是排他关系,而是在不同层次上去筛选,确定目标方法的过程。前面说过,静态方法会在类加载时就进行解析,而静态方法显然也是可以拥有重载版本的,选择重载版本也是通过静态分派来完成的。


动态分派

动态分派的典型应用是重写,运行期根据方法接收者的实际类型来选择方法。invokevirtual指令的工作过程是,优先寻找当前类中是否有该方法,如有直接选择该方法,若没有找到,则在父类中寻找,直到找到为止。