1、基础知识点
1.1 分派
定义:确定执行哪个方法的过程
分类:静态分派 & 动态分派
a. 疑问
方法的执行不是取决于代码设置中的执行对象吗?为什么还要选择呢?
b. 回答
- 若 一个对象对应于多个方法 时,就需要进行选择了
- Java中的特性:多态,即重写 & 重载。
1.2 变量的静态类型 & 动态类型
1 | public class Test { |
变量的静态类型 = 引用类型 :不会被改变、在编译器可知
变量的动态类型 = 实例对象类型 :会变化、在运行期才可知
2、静态分派
定义:
- 根据变量的静态类型进行方法分派的行为即根据 变量的静态类型确定执行哪个方法
- 发生在编译期,所以不由 Java 虚拟机来执行
应用场景
方法重载(OverLoad) = 静态分派 = 根据 变量的静态类型确定执行(重载)哪个方法。
什么是重载?
如果我们想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同。这些方法之间的关系,我们称之为重载。重载的方法在编译过程中即可完成识别。具体到每一个方法调用,Java 编译器会根据所传入参数的声明类型(注意与实际类型区分)来选取重载方法。选取的过程共分为三个阶段:
- 在不考虑对基本类型自动装拆箱(auto-boxing,auto-unboxing),以及可变长参数的情况下选取重载方法;
- 如果在第 1 个阶段中没有找到适配的方法,那么在允许自动装拆箱,但不允许可变长参数的情况下选取重载方法;
- 如果在第 2 个阶段中没有找到适配的方法,那么在允许自动装拆箱以及可变长参数的情况下选取重载方法。
如果 Java 编译器在同一个阶段中找到了多个适配的方法,那么它会在其中选择一个最为贴切的,而决定贴切程度的一个关键就是形式参数类型的继承关系。
1 | public class Test { |
特别注意
a. 变量的静态类型发生变化的情况
可通过强制类型转换改变变量的静态类型
1 | Human man = new Man(); |
b. 静态分派的优先级匹配问题
问题描述:
背景:现需要进行静态分派
问题:程序中没有显示指 静态类型
解决方案:程序会根据静态类型的优先级从而选择优先的静态类型进行方法分配。
特别注意
- 上面讲解的主要是基本数据类型的优先级匹配问题
- 若是引用类型,则根据继承关系进行优先级匹配
注意只跟其编译时类型(即静态类型)相关
优先级顺序为:
- char > int > long > float > double > Character > Serializable > Object >…
- 其中…为变长参数,将其视为一个数组元素。变长参数的重载优先级最低。 因为 char 转型到 byte 或 short 的过程是不安全的 所以不会选择参数类型为byte
1 | //比如: |
3、动态分派
3.1 定义
根据 变量的动态类型进行方法分派的行为
即根据 变量的动态类型确定执行哪个方法
3.2 应用场景
方法重写(Override)
如果子类定义了与父类中非私有、非静态方法同名的方法,那么只有当这两个方法的参数类型以及返回类型一致,Java 虚拟机才会判定为重写。
对于 Java 语言中重写而 Java 虚拟机中非重写的情况,编译器会通过生成桥接方法 来实现 Java 中的重写语义。
由于对重载方法的区分在编译阶段已经完成,我们可以认为 Java 虚拟机不存在重载这一概念。
确切地说,Java 虚拟机中的静态绑定指的是在解析时便能够直接识别目标方法的情况,而动态绑定则指的是需要在运行过程中根据调用者的动态类型来识别目标方法的情况。
1 | // 定义类 |
invokevirtual指令执行的第一步 = 确定接受者的实际类型
invokevirtual指令执行的第二步 = 将 常量池中 类方法符号引用 解析到不同的直接引用上
第二步即方法重写(Override)的本质
1 | 例子:静态+动态 |
1 | 例子:动态+初始化编译 |
4、单分派与多分派
方法的接收者、方法的参数都可以称为方法的宗量。根据分批基于多少种宗量,可以将分派划分为单分派和多分派。单分派是根据一个宗量对目标方法进行选择的,多分派是根据多于一个的宗量对目标方法进行选择的。
Java在进行静态分派时,选择目标方法要依据两点:一是变量的静态类型是哪个类型,二是方法参数是什么类型。因为要根据两个宗量进行选择,所以Java语言的静态分派属于多分派类型。
运行时阶段的动态分派过程,由于编译器已经确定了目标方法的签名(包括方法参数),运行时虚拟机只需要确定方法的接收者的实际类型,就可以分派。因为是根据一个宗量作为选择依据,所以Java语言的动态分派属于单分派类型。
5、虚拟机动态分派的实现
由于动态分派是非常频繁的动作,而动态分派在方法版本选择过程中又需要在方法元数据中搜索合适的目标方法,虚拟机实现出于性能的考虑,通常不直接进行如此频繁的搜索,而是采用优化方法。
其中一种“稳定优化”手段是:在类的方法区中建立一个虚方法表(Virtual Method Table, 也称vtable, 与此对应,也存在接口方法表——Interface Method Table,也称itable)。使用虚方法表索引来代替元数据查找以提高性能。其原理与C++的虚函数表类似。
通过虚方法表存放各个方法的实际入口地址,如果某个方法在子类中没有被重写,那子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的,都指向父类的实现入口;如果子类中重写了这个方法,子类方法表中的地址将会诶替换为指向子类实现版本的入口地址。