JAVA方法调用-封装与继承中,执行的字节码是如何确定的
JAVA动态扩展能力的来源
JAVA之核心,是封装、多态、继承。
具体一点,就是:
1、数据和方法的封装
2、方法的多态和类的多态
3、单继承(可突破)的实现
第1点和第3点,其他语言都有,故不赘言
第二点,方法的多态,今天我们说一下:
方法的多态,最重要的是在方法重载与方法重写都存在的情况下确定要调用哪一个方法。
方法调用阶段的唯一任务就是 确定被调用方法的版本(即调用哪一个方法) ,暂时不涉及方法内部的具体运行过程。
class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在class文件中存储的都是符号引用,而不是方法在实际运行时内存布局中的入口地址,这使得java有着更强大的动态扩展能力,但也使得java方法的调用过程变得相对复杂起来,需要在类的加载甚至运行期间才能确定目标方法的直接引用。
JAVA的调用方式有两种:解析调用和分派调用
解析调用 :
之前说到,所有方法在class文件里面都是一个常量池中的符号引用,在 类加载的解析阶段 ,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是, 方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的 。
符合这个条件的有 静态方法,私有方法,实例构造器和父类方法 四类,它们在 类加载的时候会把符号引用解析为该方法的直接引用。
解析调用一定是一个静态的过程,编译期间就完全确定,在类装载的解析阶段就会把涉及到的符号引用全部转化为可确定的直接引用,不会延迟到运行期间再去完成。
分派调用 :分派调用可能是静态的也可能是动态的,根据分派依据的宗量数又可分为单分派和多分派。分派机制与java的多态机制关系密切。
1.静态分派 : 依赖静态类型来定位方法执行版本的分派动作,称为静态分派。静态分派的最典型的应 用就是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的
2.动态分派: 在运行期间根据实际类型来确定方法执行版本的分派调用过程称为动态分派。这跟多态性的另一个体现——重写有着很密切的关联。
3.单分派: 根据一个宗量对目标方法进行选择
4.多分派: 根据多于一个的总量对目标方法进行选择。
注:方法的接收者与方法的参数统称为方法的宗量。
JVM提供了4条方法调用的字节码指令:
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法,私有方法和父类方法
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
除此以外(除静态方法,实例构造器,私有方法,父类方法以外)其他方法称为虚方法。
单分派和多分派
方法的接收者与方法的参数统称为方法的宗量。
根据分派基于多少种宗量,可以将分派划分为单分派和多分派两种
单分派是根据一个宗量对目标方法进行选择,
多分派是根据多于一个宗量对目标方法进行选择。
————————————————————————————————————————————————————————————————-
一般的JAVA调用中,同时有重载和重写发生的情况
1、根据之前方法调用可能生成的4种字节码,找到对应方法可能生成的字节码,再根据字节码解析过程进行判断。
2、首先进行静态分派,生成相应的字节码,在常量池中生成对应的方法符号引用,这个过程根据了两个宗量进行选择(接收者和参数),因此静态分派是多分派类型。
3、再进行动态分派,将符号引用变成直接引用时,只对方法的接收者进行选择,因此只有一个宗量,动态分派是单分派。
————————————————————————————————————————————————————————————————-
理解JAVA方法调用过程,有利于理解JAVA中:多态和继承的实现原理