zoukankan      html  css  js  c++  java
  • Java中的静态分派与动态分派

    本文是《深入理解Java虚拟机》8.3.2节的读书笔记,理解有误的地方,欢迎指正

    首先是两个概念:

    • 静态类型,即是变量声明时的类型。
    • 实际类型,变量实例化时采用的类型。

    比如我们有这样一段代码

    class Human {}
    public class Man extends Human {
        public static void main(String[] args) {
            Human man = new Man();
        }
    }

    我们就称变量 man 的静态类型为 Human,实际类型为 Man。

    静态分派

    所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(根据参数的静态类型来定位目标方法)。

    静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。

    动态分派

    在运行期根据实际类型确定方法执行版本。

    比如这样一段代码,其中man和woman的静态类型都是Human,但是实际类型各异:

    public class DynamicDispatch {
        public static void main(String[] args) {
            Human man = new Man();
            Human woman = new Woman();
            man.sayHello();        // Output : man say hello
            woman.sayHello();      // Output : woman say hello
        }
        private static abstract class Human {
                protected abstract void sayHello();
            }
        private static class Man extends Human {
            protected void sayHello() {
                System.out.println("man say hello");
            }
        }
        private static class Woman extends Human {
            protected void sayHello() {
                System.out.println("woman say hello");
            }
        }
    }

    编译过后所得的字节码文件如下:

    (常量池的部分内容)
    Constant pool:
       #1 = Methodref          #8.#23         // java/lang/Object."<init>":()V
       #2 = Class              #24            // DynamicDispatch$Man
       #3 = Methodref          #2.#25         // DynamicDispatch$Man."<init>":(LDynamicDispatch$1;)V
       #4 = Class              #26            // DynamicDispatch$Woman
       #5 = Methodref          #4.#25         // DynamicDispatch$Woman."<init>":(LDynamicDispatch$1;)V
       #6 = Methodref          #13.#27        // DynamicDispatch$Human.sayHello:()V
    public class DynamicDispatch {
      public DynamicDispatch();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
      public static void main(java.lang.String[]);
        Code:
           0: new           #2                  // class DynamicDispatch$Man
           3: dup
           4: aconst_null
           5: invokespecial #3                  // Method DynamicDispatch$Man."<init>":(LDynamicDispatch$1;)V
           8: astore_1
           9: new           #4                  // class DynamicDispatch$Woman
          12: dup
          13: aconst_null
          14: invokespecial #5                  // Method DynamicDispatch$Woman."<init>":(LDynamicDispatch$1;)V
          17: astore_2
          18: aload_1                        // 将第二个引用类型本地变量(man)推送至操作数栈栈顶
          19: invokevirtual #6     // Method DynamicDispatch$Human.sayHello:()V,调用#6代表的实例方法,并且方法的接收者就是操作数栈顶元素
          22: aload_2
          23: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
          26: return
    }

    在main函数中,0~8是创建Man对象并赋到man,9~17是创建woman对象并赋到woman,18就是将man对象压入操作数栈栈顶,接下来19调用Human的sayHello方法,执行的字节码指令是invokevirtual,其具有多态查找过程,在运行时的解析过程大致为:

    1.  找到操作数栈顶的第一个元素所指向的对象的实际类型,记为C。
    2.  如果在类型C中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果权限校验不通过,返回java.lang.IllegalAccessError异常。
    3.  否则,按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程。
    4.  如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodError异常。

    这里方法的接收者——即操作数栈栈顶元素是man对象,最后的结果就是调用了Man中的sayHello方法。同样的,对woman对象的方法调用也是如此,最后执行的是Woman中的sayHello方法。

    由于invokevirtual指令执行的第一步就是在运行期间确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这个过程就是Java方法重写的本质。

  • 相关阅读:
    【计算机组成】运算器与运算方法
    【计算机组成】数据表示
    【计算机组成】概论
    【Python】基础总结
    Robot Framework(14)- Variables 表的详细使用和具体例子
    Robot Framework(13)- RF 循环的详细使用
    Robot Framework(12)- 详细解读 RF 的变量和常量
    Robot Framework(11)- 用户关键字的详解
    Robot Framework(10)- 使用资源文件
    Robot Framework(9)- 使用变量文件
  • 原文地址:https://www.cnblogs.com/read-the-spring-and-autumn-annals-in-night/p/12041950.html
Copyright © 2011-2022 走看看