关于后端:深入浅出JVM五之Java中方法调用

43次阅读

共计 5158 个字符,预计需要花费 13 分钟才能阅读完成。

本篇文章将围绕 Java 中办法的调用,深入浅出的阐明办法调用的指令、解析调用以及分派调用等

办法调用

要晓得 Java 中办法调用惟一目标就是确定要调用哪一个办法

办法调用能够分为 解析调用和分派调用,接下来会具体介绍

非虚办法与虚办法

非虚办法: 静态方法,公有办法,父类中的办法,被 final 润饰的办法,实例结构器

其余不是非虚办法的办法就是虚办法

非虚办法的特点就是没有重写办法,适宜 在类加载阶段就进行解析(符号援用 -> 间接援用)【编译时就可能确定】

调用指令

  • 一般调用指令

    • invokestatic: 调用静态方法
    • invokespecial: 调用公有办法, 父类中的办法, 实例结构器 <init> 办法,final 办法
    • invokeinterface: 调用接口办法
    • invokevirtual: 调用虚办法

    应用 invokestaticinvokespecial指令的肯定是非虚办法

    应用 invokeinterface 指令肯定是虚办法(因为接口办法须要具体的实现类去实现)

    应用 invokevirtual 指令的是虚办法

  • 动静调用指令

    • invokedynamic: 动静解析出须要调用的办法再执行

    jdk 7 呈现invokedynamic,反对动静语言

测试虚办法代码

  • 父类
 public class Father {public static void staticMethod(){System.out.println("father static method");
     }
 ​
     public final void finalMethod(){System.out.println("father final method");
     }
 ​
     public Father() {System.out.println("father init method");
     }
 ​
     public void overrideMethod(){System.out.println("father override method");
     }
 }
  • 接口
 public interface TestInterfaceMethod {void testInterfaceMethod();
 }
  • 子类
 public class Son extends Father{
 ​
     public Son() {
         //invokespecial 调用父类 init 非虚办法
         super();
         //invokestatic 调用父类静态方法 非虚办法
         staticMethod();
         //invokespecial 调用子类公有办法 非凡的非虚办法
         privateMethod();
         //invokevirtual 调用子类的重写办法 虚办法
         overrideMethod();
         //invokespecial 调用父类办法 非虚办法
         super.overrideMethod();
         //invokespecial 调用父类 final 办法 非虚办法
         super.finalMethod();
         //invokedynamic 动静生成接口的实现类 动静调用
         TestInterfaceMethod test = ()->{System.out.println("testInterfaceMethod");
         };
         //invokeinterface 调用接口办法 虚办法
         test.testInterfaceMethod();}
 ​
     @Override
     public void overrideMethod(){System.out.println("son override method");
     }
 ​
     private void privateMethod(){System.out.println("son private method");
     }
 ​
     public static void main(String[] args) {new Son();
     }
 }

留神: 接口中的默认办法也是invokeinterface, 接口中的静态方法是invokestatic

解析调用

解析调用就是在调用非虚办法

在编译期间就可能确定,运行时也不会扭转

分派调用

分派调用又分为动态分派与动态分配

晚期绑定: 解析调用和动态分派这种编译期间能够确定调用哪个办法

早期绑定: 动静分派这种编译期无奈确定, 要到运行时能力确定调用哪个办法

动态分派
   // 动态类型         理论类型
     List list = new ArrayList();

动态分派: 依据动态类型决定办法执行的版本的分派

产生在编译期,非凡的解析调用

典型的体现就是办法的重载

 public class StaticDispatch {public void test(List list){System.out.println("list");
     }
 ​
     public void test(ArrayList arrayList){System.out.println("arrayList");
     }
 ​
     public static void main(String[] args) {ArrayList arrayList = new ArrayList();
         List list = new ArrayList();
         StaticDispatch staticDispatch = new StaticDispatch();
         staticDispatch.test(list);
         staticDispatch.test(arrayList);
     }
 }
 /*
 list
 arrayList
 */

办法的版本并不是惟一的, 往往只能确定一个最适宜的版本

动静分派

动静分派: 动静期依据理论类型确定办法执行版本的分派

动静分派与重写有着严密的分割

 public class DynamicDispatch {public static void main(String[] args) {Father father = new Father();
         Father son = new Son();
 ​
         father.hello();
         son.hello();}
     static class Father{public void hello(){System.out.println("Father hello");
         }
     }
 ​
     static class Son extends Father{
         @Override
         public void hello() {System.out.println("Son hello");
         }
     }
 }
 /*
 Father hello
 Son hello
 */

尽管常量池中的符号援用雷同,invokevirtual指令最终指向的办法却不一样

剖析 invokevirtual 指令搞懂它是如何确定调用的办法

  1. invokevirtual 找到栈顶元素的 理论类型
  2. 如果在这个理论类型中找到与常量池中描述符与简略名称相符的办法,并通过拜访权限的验证就返回这个办法的援用 (未通过权限验证返回IllegalAccessException 非法拜访异样)
  3. 如果在理论类型中未找到,就去理论类型的父类中寻找 (没找到抛出AbstractMethodError 异样)

因而,子类重写父类办法时,依据 invokevirtual 指令规定,先在理论类型(子类)中寻找,找不到才去父类,所以存在重写的多态

频繁的动静分派会从新查找栈顶元素理论类型,会影响执行效率

为进步性能,JVM 在该类办法区建设虚办法表应用索引表来代替查找

字段不存在多态

当子类呈现与父类雷同的字段, 子类会笼罩父类的字段

 public class DynamicDispatch {public static void main(String[] args) {Father son = new Son();
     }
     static class Father{
         int num = 1;
 ​
         public Father() {hello();
         }
 ​
         public void hello(){System.out.println("Father hello" + num);
         }
     }
 ​
     static class Son extends Father{
         int num = 2;
 ​
         public Son() {hello();
         }
 ​
         @Override
         public void hello() {System.out.println("Son hello"+ num);
         }
     }
 }
 /*
 Son hello 0
 Son hello 2
 */

先对父类进行初始化,所以会先执行父类中的构造方法,而构造方法去执行了 hello() 办法,此时的理论类型是 Son 于是会去执行 Son 的 hello 办法,此时子类还未初始化成员变量,只是有个默认值,所以输入Son hello 0

单分派与多分派

办法参数或办法调用者被称为宗量

分派还能够分为单、多分派

依据一个宗量(办法参数或办法调用者)抉择办法被称为单分派

依据多个宗量(办法参数和办法调用者)抉择办法被称为多分派

 public class DynamicDispatch {public static void main(String[] args) {Father son = new Son();
         Father father = new Father();
 ​
         son.hello(new Nod());
         father.hello(new Wave());
     }
     static class Father{
 ​
 ​
         public void hello(Nod nod){System.out.println("Father nod hello");
         }
 ​
         public void hello(Wave wave){System.out.println("Father wave hello");
         }
     }
 ​
     static class Son extends Father{
 ​
         @Override
         public void hello(Nod nod) {System.out.println("Son nod hello");
         }
 ​
         @Override
         public void hello(Wave wave) {System.out.println("Son wave hello");
         }
     }
 ​
     // 招手
     static class Wave{}
     // 拍板
     static class Nod{}}
 /*
 Son nod hello
 Father wave hello 
 */

在编译时,不仅要关怀动态类型是 Father 还是 Son,还要关怀参数是 Nod 还是 Wave,所以 动态分派是多分派(依据两个宗量对办法进行抉择)

在执行 son.hello(new Nod()) 时只须要关怀理论类型是 Son 还是 Father,所以 动静分派是单分派(依据一个宗量对办法进行抉择)

总结

本篇文章围绕 Java 办法的调用,深入浅出的解析非虚办法与虚办法、调用的字节码指令、解析调用和分派调用、单分派以及多分派

不能重写的办法(动态、公有、父类、final 润饰、实例结构)被称为非虚办法,其余办法为虚办法

非虚办法是编译时就可能确定的,解析调用就是调用非虚办法

分派调用中的动态分派也是编译时确定的,是非凡的解析调用,依据动态类型抉择办法,典型例子就是办法重载

分派调用中的动静分派是依据理论类型抉择办法,在运行时才可能确定理论类型,典型例子就是办法重写

动态分派须要思考办法调用者和办法参数是多分派,动静分派只须要思考办法调用者是单分派

最初(不要白嫖,一键三连求求拉 \~)

本篇文章笔记以及案例被支出 gitee-StudyJava、github-StudyJava 感兴趣的同学能够 stat 下继续关注喔 \~

有什么问题能够在评论区交换,如果感觉菜菜写的不错,能够点赞、关注、珍藏反对一下 \~

关注菜菜,分享更多干货,公众号:菜菜的后端私房菜

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0