*****标题写的是介绍虚拟机栈中的动态链接,但是这里讲解的是
动态链接
和
静态链接
的区别*****
一.概述:
Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以指向常量池的引用作为参数
-
部分符号引用在类加载阶段(解析)的时候就转化为直接引用,这种转化为
静态链接
-
部分符号引用在运行期间转化为直接引用,这种转化为
动态链接
二.虚/非虚方法
1.非虚方法:
在类加载阶段(解析时期)才会将符号引用解析直接引用的方法被称之为
非虚方法
- 静态方法、私有方法、实例构造器、父类方法,这些只需要在类加载阶段就会确定
- 解析调用一定是一个静态过程
- 编译器可知,运行期不可变
2.虚方法:
在运行期间将符号引用转化为直接引用的方法被称之为虚方法
三.分派
首先先了解两个概念静态类型和实际类型
public class Ocean {
public static void main(String[] args) {
Ocean river = new River();
}
}
class River extends Ocean{
}
Ocean river = new River();
静态类型:编译期间确定的类型(Ocean)
实际类型:运行期间确定的类型(River)
**********左为静态类型,右为实际类型
再了解一个概念宗量:方法的接受者(亦即方法的调用者)与方法的参数统称为方法的宗量
1.静态分派
class Ocean {
}
class River extends Ocean{
}
class Lake extends Ocean{
}
/**
* 静态分派
*/
public class StaticDispatch{
public void getSize(Ocean waterArea){
System.out.println("Ocean is the biggest!");
}
public void getSize(Lake waterArea){
System.out.println("Lake is bigger!");
}
public void getSize(River waterArea){
System.out.println("River is big...");
}
public static void main(String[] args) {
StaticDispatch dispatch = new StaticDispatch();
Ocean river = new River();
Ocean lake = new Lake();
dispatch.getSize(lake);
dispatch.getSize(river);
}
}
控制台输出:
Ocean is the biggest!
Ocean is the biggest!
分析:
①定义了两个静态类型相同而实际类型却不同的变量
②重载时方法的执行依赖的是形参列表,而形参列表指的却是静态类型
③静态类型是在类加载(解析时期)就确定下来的
总结:静态分派可以解释重载
2.动态分派
abstract class Human {
abstract void call();
}
class Father extends Human{
@Override
void call() {
System.out.println("I am the Father!");
}
}
class Mother extends Human{
@Override
void call() {
System.out.println("I am the Mother!");
}
}
public class DynamicDispatch {
public static void main(String[] args) {
Human father = new Father();
Human mother = new Mother();
father.call();
mother.call();
}
}
控制台输出:
I am the Father!
I am the Mother!
程序执行过程的机器指令分析如下:
各操作指令解析:
- 0:在java堆中为变量father分配空间,并将地址压入操作数栈顶
- 3:复制操作数栈顶值.并压入栈顶(此时操作栈上有两个连续相同的father对象地址)
- 4:从操作栈顶弹出一个this的引用(即两个连续father对象地址中靠近栈顶的一个),并调用实例化方法<init>:()v
- 7:将栈顶的仅剩的一个father对象地址存入第二个本地变量表slot(1)中
- 8~15:重复上面的操作,创建了mother对象并将其地址存入第三个本地变量表slot(2)中
- 16.将第二个本地变量表slot(1)中引用类型数据father地址推送至操作栈顶
- 17:调用虚方法,根据ather对象地址查询其call()方法并执行
- 20~21:重复上面的操作,根据mother对象地址查询其call()并执行
- 24:结束方法
总结:从上面的invokevirtual可以知道方法call()的符号引用转换是在运行时期完成的,所以可以说动态分派解释了重载
3.单分派&多分派
单分派是指根据一个宗量就可以知道调用目标()即应该调用哪个方法)
多分派需要根据多个宗量才能确定调用目标
总结:
java的静态分派属于多分派类型
java的动态分派属于单分派类型
即
Java 语言是一门静态多分派(方法重载)、动态单分派(方法重写)的语言.