美文网首页
多态->分派

多态->分派

作者: 冉桓彬 | 来源:发表于2019-06-07 12:44 被阅读0次

摘抄深入理解java虚拟机
目标

1. 静态分派
2. 动态分派
3. 单分派与多分派

分派包括: 静态分派与动态分派两种类型, 静态分派的使用场景为重载, 动态分派使用场景为重写.

类型 方法调用依据 发生阶段 应用场景
静态分派 方法中参数的静态类型 编译期 重载
动态分派 方法调用者的实际类型 运行期 重写

根据两者的区别分别分析静态分派动态分派.

一、方法调用

  方法调用并不等同于方法执行, 方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法), 暂时还不涉及方法内部的具体运行过程. 一切方法调用在Class文件里面存储的都只是符号引用, 而不是方法在实际运行时内存布局中的入口地址(相当于之前说的直接引用).

1.1 解析

  所有方法调用中的目标方法在Class文件里面都是一个常量池的符号引用, 在类加载的解析阶段, 会将其中的一部分符号引用转化为直接引用, 这种解析能成立的前提是: 方法在程序真正运行之前就有一个可确定的调用版本, 并且这个方法的调用版本在运行期是不可改变的. 换句话说, 调用目标在程序代码写好、编译器进行编译时就必须确定下来, 这类方法的调用称为解析(Resolution).

二、静态分派

  使用场景: 重载

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, Man!");
    }
    public void sayHello(Woman guy) {
        System.out.println("hello, Man!");
    }
    public static void main(String[] args) {
        Human man = new Man();
        Human woman = new Woman();
        StaticDispatch sr = new StaticDispatch();
        sr.sayHello(man);
        sr.sayHello(woman );
    }
}

输出结果:

hello, guy!
hello, guy!

  Human称为变量的静态类型, Man称为变量的实际类型, 静态类型是在编译期可知的, 实际类型变化的结果在运行期才可确定, 编译器在编译程序的时候并不知道一个对象的实际类型是什么.
  关于重载, 运行时使用哪个重载版本, 完全取决于传入参数的数量和数据类型, 编译器在重载时是通过参数的静态类型而不是实际类型作为判断依据的, 并且静态类型是编译期可知的, 因此在编译阶段, Javac编译器会根据参数的静态类型决定使用哪个重载版本, 所以选择了sayHello(Human)作为调用目标, 并把这个方法的符号引用写到main()方法里面的两条invokevirtual指令的参数中(关于这段话, 通过下文字节码文件可以更好的理解).
  感觉书里面写的好像还是有点儿晦涩, 然后找到了一个概念摘抄自RednaxelaFX, 函数重载是编译时概念, 参数类型是由变量声明的类型所决定的.
然后用javap -verbose打开上面的字节码文件:字节码文件比较长, 只列出关键的几项

public class algorithm.test.StaticDispatch
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #13.#43        // java/lang/Object."<init>":()V
   #2 = Fieldref           #44.#45        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #46            // hello, guy!
   #4 = Methodref          #47.#48        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = String             #49            // hello, Man!
   #6 = Class              #50            // algorithm/test/StaticDispatch$Man
   #7 = Methodref          #6.#43         // algorithm/test/StaticDispatch$Man."<init>":()V
   #8 = Class              #51            // algorithm/test/StaticDispatch$Woman
   #9 = Methodref          #8.#43         // algorithm/test/StaticDispatch$Woman."<init>":()V
  #10 = Class              #52            // algorithm/test/StaticDispatch
  #11 = Methodref          #10.#43        // algorithm/test/StaticDispatch."<init>":()V
  #12 = Methodref          #10.#53        // algorithm/test/StaticDispatch.sayHello:(Lalgorithm/test/StaticDispatch$Human;)V
  #13 = Class              #54            // java/lang/Object
  #14 = Utf8               Woman
  #15 = Utf8               InnerClasses
  #16 = Utf8               Man
  #17 = Class              #55            // algorithm/test/StaticDispatch$Human
  #18 = Utf8               Human
  #19 = Utf8               <init>
  #20 = Utf8               ()V
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               LocalVariableTable
  #24 = Utf8               this
  #25 = Utf8               Lalgorithm/test/StaticDispatch;
  #26 = Utf8               sayHello
  #27 = Utf8               (Lalgorithm/test/StaticDispatch$Human;)V
  #28 = Utf8               guy
  #29 = Utf8               Lalgorithm/test/StaticDispatch$Human;
  #30 = Utf8               (Lalgorithm/test/StaticDispatch$Man;)V
  #31 = Utf8               Lalgorithm/test/StaticDispatch$Man;
  #32 = Utf8               (Lalgorithm/test/StaticDispatch$Woman;)V
  #33 = Utf8               Lalgorithm/test/StaticDispatch$Woman;
  #34 = Utf8               main
  #35 = Utf8               ([Ljava/lang/String;)V
  #36 = Utf8               args
  #37 = Utf8               [Ljava/lang/String;
  #38 = Utf8               man
  #39 = Utf8               woman
  #40 = Utf8               sr
  #41 = Utf8               SourceFile
  #42 = Utf8               StaticDispatch.java
  #43 = NameAndType        #19:#20        // "<init>":()V
  #44 = Class              #56            // java/lang/System
  #45 = NameAndType        #57:#58        // out:Ljava/io/PrintStream;
  #46 = Utf8               hello, guy!
  #47 = Class              #59            // java/io/PrintStream
  #48 = NameAndType        #60:#61        // println:(Ljava/lang/String;)V
  #49 = Utf8               hello, Man!
  #50 = Utf8               algorithm/test/StaticDispatch$Man
  #51 = Utf8               algorithm/test/StaticDispatch$Woman
  #52 = Utf8               algorithm/test/StaticDispatch
  #53 = NameAndType        #26:#27        // sayHello:(Lalgorithm/test/StaticDispatch$Human;)V
  #54 = Utf8               java/lang/Object
  #55 = Utf8               algorithm/test/StaticDispatch$Human
  #56 = Utf8               java/lang/System
  #57 = Utf8               out
  #58 = Utf8               Ljava/io/PrintStream;
  #59 = Utf8               java/io/PrintStream
  #60 = Utf8               println
  #61 = Utf8               (Ljava/lang/String;)V
{
    public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #6                  // class algorithm/test/StaticDispatch$Man
         3: dup
         4: invokespecial #7                  // Method algorithm/test/StaticDispatch$Man."<init>":()V
         7: astore_1
         8: new           #8                  // class algorithm/test/StaticDispatch$Woman
        11: dup
        12: invokespecial #9                  // Method algorithm/test/StaticDispatch$Woman."<init>":()V
        15: astore_2
        16: new           #10                 // class algorithm/test/StaticDispatch
        19: dup
        20: invokespecial #11                 // Method "<init>":()V
        23: astore_3
        24: aload_3
        25: aload_1
        26: invokevirtual #12                 // Method sayHello:(Lalgorithm/test/StaticDispatch$Human;)V
        29: aload_3
        30: aload_2
        31: invokevirtual #12                 // Method sayHello:(Lalgorithm/test/StaticDispatch$Human;)V
        34: return
      LineNumberTable:
        line 27: 0
        line 28: 8
        line 29: 16
        line 30: 24
        line 31: 29
        line 32: 34
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      35     0  args   [Ljava/lang/String;
            8      27     1   man   Lalgorithm/test/StaticDispatch$Human;
           16      19     2 woman   Lalgorithm/test/StaticDispatch$Human;
           24      11     3    sr   Lalgorithm/test/StaticDispatch;
}

  上面重点关注main()里面的2631两个指令, 然后对应Class常量池中的#12, 然后再反过来理解上文中摘抄的那段话, 编译期确定调用目标, 然后把对应方法的符号引用写到main()方法的两条invokevirtual指令的参数中.
通过Class的Constant pool:可以看到26和31的调用路径为: #12 -> #10、#53 -> #52、#26、#27, 结合这个调用链可以很直观的看到: 被确定的目标方法为sayHello(Human), 其对应的符号引用为#53, 而#53又被写入到了#12中, 而main方法中的invokevirtual指令对应常量池中的符号引用#12

三、动态分派
  使用场景: 重写
  是用动态分派还是静态分派? 向上转型后调用子类覆写的方法便是一个很好地说明动态分派的例子, 此时在判断执行父类中的方法还是子类中覆盖的方法时, 如果用静态类型来判断, 那么无论怎么进行向上转型, 都只会根据静态类型来进行判断, 然后只会调用父类中的方法, 但实际情况是根据父类实例化的子类不同, 调用的是不同子类中覆写的方法, 这里要根据变量的实际类型来分派方法的执行版本, 而实际类型的确定需要在程序运行时才能确定下来, 这里在运行期根据实际类型确定方法执行版本的分派过程称为动态分派.

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

输出结果

Man say hello
Woman say hello

javap -verbose DynamicDispatch.class

public class algorithm.test.DynamicDispatch
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#30         // java/lang/Object."<init>":()V
   #2 = Class              #31            // algorithm/test/DynamicDispatch$Man
   #3 = Methodref          #2.#30         // algorithm/test/DynamicDispatch$Man."<init>":()V
   #4 = Class              #32            // algorithm/test/DynamicDispatch$Woman
   #5 = Methodref          #4.#30         // algorithm/test/DynamicDispatch$Woman."<init>":()V
   #6 = Methodref          #12.#33        // algorithm/test/DynamicDispatch$Human.sayHello:()V
   #7 = Class              #34            // algorithm/test/DynamicDispatch
   #8 = Class              #35            // java/lang/Object
   #9 = Utf8               Woman
  #10 = Utf8               InnerClasses
  #11 = Utf8               Man
  #12 = Class              #36            // algorithm/test/DynamicDispatch$Human
  #13 = Utf8               Human
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lalgorithm/test/DynamicDispatch;
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               man
  #26 = Utf8               Lalgorithm/test/DynamicDispatch$Human;
  #27 = Utf8               woman
  #28 = Utf8               SourceFile
  #29 = Utf8               DynamicDispatch.java
  #30 = NameAndType        #14:#15        // "<init>":()V
  #31 = Utf8               algorithm/test/DynamicDispatch$Man
  #32 = Utf8               algorithm/test/DynamicDispatch$Woman
  #33 = NameAndType        #37:#15        // sayHello:()V
  #34 = Utf8               algorithm/test/DynamicDispatch
  #35 = Utf8               java/lang/Object
  #36 = Utf8               algorithm/test/DynamicDispatch$Human
  #37 = Utf8               sayHello
{
    public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class algorithm/test/DynamicDispatch$Man
         3: dup
         4: invokespecial #3                  // Method algorithm/test/DynamicDispatch$Man."<init>":()V
         7: astore_1
         8: new           #4                  // class algorithm/test/DynamicDispatch$Woman
        11: dup
        12: invokespecial #5                  // Method algorithm/test/DynamicDispatch$Woman."<init>":()V
        15: astore_2
        16: aload_1
        17: invokevirtual #6                  // Method algorithm/test/DynamicDispatch$Human.sayHello:()V
        20: aload_2
        21: invokevirtual #6                  // Method algorithm/test/DynamicDispatch$Human.sayHello:()V
        24: return
      LineNumberTable:
        line 24: 0
        line 25: 8
        line 26: 16
        line 27: 20
        line 28: 24
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      25     0  args   [Ljava/lang/String;
            8      17     1   man   Lalgorithm/test/DynamicDispatch$Human;
           16       9     2 woman   Lalgorithm/test/DynamicDispatch$Human;
}

invokevirtual指令的运行时解析过程大致分为以下几个步骤:

  • 1、找到操作数栈顶的第一个元素所指向的对象的实际类型, 记作C
  • 2、如果类型C中找到与常量中的描述符和简单名称都相符的方法, 则进行访问权限校验, 如果通过则返回这个方法的直接引用, 查找过程结束
  • 3、否则, 按照继承关系从下往上依次对C的各个父类件第二步的搜索和验证过程

分析一下DynamicDispatch.class的main方法的流程

  • 1、<0、3、4>完成的流程为new Man()
  • 2、<7>完成变量的装载操作, astore_1对应LocalVariableTable中Slot_1, 也就是将new Man()指向Human man
  • 3、<0、3、4、7>完成Human man = new Man()的操作
  • 4、同理<8、11、12、15>完成Human woman = new Woman()的操作
  • 5、接下来看<16、17>, 16是aload_1也就是将Slot_1对应的man压入到栈顶, 然后结合invokevirtual的第二点, 从栈顶元素(这里也就是aload_1压入的Human man元素实际指向的是new Man)中查找是否存在目标方法, 如果存在则校验, 通过后直接返回该方法的直接引用, 查找结束.
  • 6、<20、21>操作桶<16、17>操作

参考文章:
https://blog.csdn.net/pange1991/article/details/82080907

相关文章

网友评论

      本文标题:多态->分派

      本文链接:https://www.haomeiwen.com/subject/trpmxctx.html