方法调用不等于方法执行, 方法调用阶段的唯一任务就是确定被调用的方法的版本. class 文件编译期间不包含传统程序的连接过程, 因此方法不是实际内存运行的入口地址, 这个特性给 java 带来了动态扩展的能力, 也使 java 的方法调用过程变得更加复杂, 需要在类加载期间甚至运行期间才能确定目标方法的引用.
1, 解析
方法调用目标方法在 class 文件里面是对常量池中的一个符号引用, 在类加载解析阶段会将其中的一部分符号引用转换成直接引用. 转换的前提是在程序真正运行前就有一个可以确定的可用版本, 并且这个方法的调用版本在运行期间不可变. 换句话说就是调用目标程序代码写好, 编译器编译期间就必须确定下来. 这类方法调用被称为解析.
符合编译期间可知, 运行期间不可变的方法只有静态方法和私有方法. 前者与类型直接关联, 后者外部不可被访问因此无法被覆盖重写, 因此适合在类加载阶段进行解析, 与之相对应的是 java 虚拟机中的 5 条直接码指令
invokestatic 静态方法调用, invokespecial 调用实例构造器 init 方法, 私有方法和类方法, invokevirtual 调用虚方法, invokeinteface 调用接口方法, 运行时确定一个具体实现类的方法, invokedynamic 先在运行动态解析出调用点限定符号所引用的方法, 再执行该方法. 前四条指令的分派逻辑在 java 虚拟机内部固化, invokedynamic 指令的分派逻辑由用户设定的引导方法决定.
解析调用是一个静态的过程, 在编译期间就完全确定, 在类装载的解析阶段就将符号引用转换成了直接引用, 不会延迟到运行期间再去完成.
2, 分派
1) 静态分派
- package org.xiaofeiyang.classloader;
- /**
- * @author: yangchun
- * @description:
- * @date: Created in 2019-11-24 11:07
- */
- public class StaticDispatch {
- static abstract class Human{
- }
- static class Man extends Human{
- }
- static class Woman extends Human{
- }
- public void sayHello(Human guy){
- System.out.println("Hello guy");
- }
- public void sayHello(Man guy){
- System.out.println("Hello gentleman");
- }
- public void sayHello(Woman guy){
- System.out.println("Hello lady");
- }
- public static void main(String[] args){
- Human man = new Man();
- Human woman = new Woman();
- StaticDispatch staticDispatch = new StaticDispatch();
- staticDispatch.sayHello(man);
- staticDispatch.sayHello(woman);
- }
- }
运行结果
- Hello guy
- Hello guy
上面 Human 类型称为变量的静态类型或者外观类型, 静态类型编译期间可知, 实际类型动态运行时才可知. 上面代码编译器通过参数的静态类型而不是实际类型确定重载方法, 所以选择
sayHello(Human guy) 作为调用目标, 并把这个方法的符号引用写到 main() 方的两条 invokedynamic 指令里面. 依赖静态类型来定位方法执行版本的分派动作称为静态分派. 静态分派发生在编译期间.
- package org.xiaofeiyang.classloader;
- import java.io.Serializable;
- /**
- * @author: yangchun
- * @description:
- * @date: Created in 2019-11-24 11:26
- */
- public class Overload {
- public static void sayHello(Object arg){
- System.out.println("Hello Objec");
- }
- public static void sayHello(int arg){
- System.out.println("Hello int");
- }
- public static void sayHello(long arg){
- System.out.println("Hello long");
- }
- public static void sayHello(Character arg){
- System.out.println("Hello Character");
- }
- public static void sayHello(char arg){
- System.out.println("Hello char");
- }
- public static void sayHello(char ...arg){
- System.out.println("Hello char..");
- }
- public static void sayHello(Serializable arg){
- System.out.println("Hello Serializable");
- }
- public static void main(String[] args){
- sayHello("a");
- }
- }
这个代码可以非常好的验证静态分派.
2) 动态分派
来源: http://www.bubuko.com/infodetail-3301560.html