《深入理解Java虚拟机》- 重载与重写

Posted chenscript

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《深入理解Java虚拟机》- 重载与重写相关的知识,希望对你有一定的参考价值。

这一节打算从“方法调用”的主题进行分析。

方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不设计方法内部的具体运行过程。

一、概念

  • 解析调用:所有方法调用中的目标方法在Class文件里都是一个常量池中的引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用。也就是说,调用目标在程序代码写好、编译器进行编译时就必须能确定下来的方法调用成为解析

小插曲:这里提供5条方法调用字节码指令

  • invokestatic:调用静态方法
  • invokespecial:调用实例构造器<init>方法、私有方法和父类方法
  • invokevirtual:调用所有的虚方法
  • invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
  • invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

       只要能被invokestatic 和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,他们在类加载的时候就会把符号引用解析为该方法的直接引用,这些方法也可以成为非虚方法。另外,在Java语言规范中明确说明了final方法也是一种非虚方法,虽然final方法是使用invokevirtual指令来调用的,但是无法被覆盖,多态的选择结果也肯定是唯一的。

  • 分派调用
    1. 静态分派(体现为重载)
    2. 动态分派(体现为重写)

二、代码

  1. 重载
    1. java代码
      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,girl");
      }
       public static void main(String... args){
         Human man = new Man();
         Human woman = new Woman();
         //Human称为变量的静态类型,后面的Man被称为实际类型。  
         StaticDispatch sr = new StaticDispatch();
         sr.sayHello(man);
         sr.sayHello(woman);
         //静态类型变化(就是在使用时改变静态类型,变量本身的静态类型不会被改变,并且最终的静态类型是在编译器可知的)
         sr.sayHello((Man)man);
         sr.sayHello((Woman)woman);
        }
      }

      输出:

      [root@localhost tmp4]# java StaticDispatch
      hello,guy
      hello,guy
      hello,man
      hello,girl

      分析:(上面的代码需要直接使用javac编译,绕过IDE的语法识别就可以了)在没有发生强制转换的时候,使用的是静态类型。当发生静态类型变化的时候,也就是使用强制转换的时候,虚拟机才会识别实际类型。

    2. 上面代码对应的字节码
      技术图片
      Constant pool:
         #1 = Methodref          #16.#34        // java/lang/Object."<init>":()V
         #2 = Fieldref           #35.#36        // java/lang/System.out:Ljava/io/PrintStream;
         #3 = String             #37            // hello,guy
         #4 = Methodref          #38.#39        // java/io/PrintStream.println:(Ljava/lang/String;)V
         #5 = String             #40            // hello,man
         #6 = String             #41            // hello,girl
         #7 = Class              #42            // StaticDispatch$Man
         #8 = Methodref          #7.#34         // StaticDispatch$Man."<init>":()V
         #9 = Class              #43            // StaticDispatch$Woman
        #10 = Methodref          #9.#34         // StaticDispatch$Woman."<init>":()V
        #11 = Class              #44            // StaticDispatch
        #12 = Methodref          #11.#34        // StaticDispatch."<init>":()V
        #13 = Methodref          #11.#45        // StaticDispatch.sayHello:(LStaticDispatch$Human;)V
        #14 = Methodref          #11.#46        // StaticDispatch.sayHello:(LStaticDispatch$Man;)V
        #15 = Methodref          #11.#47        // StaticDispatch.sayHello:(LStaticDispatch$Woman;)V
        #16 = Class              #48            // java/lang/Object
        #17 = Utf8               Woman
        #18 = Utf8               InnerClasses
        #19 = Utf8               Man
        #20 = Class              #49            // StaticDispatch$Human
        #21 = Utf8               Human
        #22 = Utf8               <init>
        #23 = Utf8               ()V
        #24 = Utf8               Code
        #25 = Utf8               LineNumberTable
        #26 = Utf8               sayHello
        #27 = Utf8               (LStaticDispatch$Human;)V
        #28 = Utf8               (LStaticDispatch$Man;)V
        #29 = Utf8               (LStaticDispatch$Woman;)V
        #30 = Utf8               main
        #31 = Utf8               ([Ljava/lang/String;)V
        #32 = Utf8               SourceFile
        #33 = Utf8               StaticDispatch.java
        #34 = NameAndType        #22:#23        // "<init>":()V
        #35 = Class              #50            // java/lang/System
        #36 = NameAndType        #51:#52        // out:Ljava/io/PrintStream;
        #37 = Utf8               hello,guy
        #38 = Class              #53            // java/io/PrintStream
        #39 = NameAndType        #54:#55        // println:(Ljava/lang/String;)V
        #40 = Utf8               hello,man
        #41 = Utf8               hello,girl
        #42 = Utf8               StaticDispatch$Man
        #43 = Utf8               StaticDispatch$Woman
        #44 = Utf8               StaticDispatch
        #45 = NameAndType        #26:#27        // sayHello:(LStaticDispatch$Human;)V
        #46 = NameAndType        #26:#28        // sayHello:(LStaticDispatch$Man;)V
        #47 = NameAndType        #26:#29        // sayHello:(LStaticDispatch$Woman;)V
        #48 = Utf8               java/lang/Object
        #49 = Utf8               StaticDispatch$Human
        #50 = Utf8               java/lang/System
        #51 = Utf8               out
        #52 = Utf8               Ljava/io/PrintStream;
        #53 = Utf8               java/io/PrintStream
        #54 = Utf8               println
        #55 = Utf8               (Ljava/lang/String;)V
      View Code

      上面是常量池。(也就是#号对上的引用)

      public static void main(java.lang.String...);
          descriptor: ([Ljava/lang/String;)V
          flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
          Code:
            stack=2, locals=4, args_size=1
               0: new           #7    // class StaticDispatch$Man
               3: dup
               4: invokespecial #8    // Method StaticDispatch$Man."<init>":()V
               7: astore_1
               8: new           #9    // class StaticDispatch$Woman
              11: dup
              12: invokespecial #10    // Method StaticDispatch$Woman."<init>":()V
              15: astore_2
              16: new           #11   // class StaticDispatch
              19: dup
              20: invokespecial #12   // Method "<init>":()V
              23: astore_3
              24: aload_3
              25: aload_1
              26: invokevirtual #13   // Method sayHello:(LStaticDispatch$Human;)V
              29: aload_3
              30: aload_2
              31: invokevirtual #13   // Method sayHello:(LStaticDispatch$Human;)V
              34: aload_3
              35: aload_1
              36: checkcast     #7    // class StaticDispatch$Man
              39: invokevirtual #14   // Method sayHello:(LStaticDispatch$Man;)V
              42: aload_3
              43: aload_2
              44: checkcast     #9    // class StaticDispatch$Woman
              47: invokevirtual #15   // Method sayHello:(LStaticDispatch$Woman;)V
              50: return
            LineNumberTable:
              line 18: 0
              line 19: 8
              line 21: 16
              line 22: 24
              line 23: 29
              line 24: 34
              line 25: 42
              line 27: 50
      }
      SourceFile: "StaticDispatch.java"
      InnerClasses:
           static #17= #9 of #11; //Woman=class StaticDispatch$Woman of class StaticDispatch
           static #19= #7 of #11; //Man=class StaticDispatch$Man of class StaticDispatch
           static abstract #21= #20 of #11; //Human=class StaticDispatch$Human of class StaticDispatch

      这里的字节码中,可以看到通过invokespecial调用类的构造方法之后,到了26行和31行使用了分派默认调用了静态类型即Human,而39行和47行前使用了强转化之后,invokevirtual调用虚方法就会分派指向实际类型。

  2. 重写
    1. java代码
      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 = new Woman();
           man.sayHello();
         }
      }

      这是简单的重写代码。输出如下:

      [root@localhost tmp5]# java DynamicDispatch
      man say hello
      Woman say hello
      Woman say hello

       

    2. 字节码
      技术图片
      [root@localhost tmp5]# javap -verbose DynamicDispatch
      Classfile /usr/local/asmtools-7.0-build/binaries/lib/tmp5/DynamicDispatch.class
        Last modified Aug 19, 2019; size 514 bytes
        MD5 checksum a9486d1c7dc75a210b82bd18f1782dfa
        Compiled from "DynamicDispatch.java"
      public class DynamicDispatch
        minor version: 0
        major version: 52
        flags: ACC_PUBLIC, ACC_SUPER
      Constant pool:
         #1 = Methodref          #8.#22         // java/lang/Object."<init>":()V
         #2 = Class              #23            // DynamicDispatch$Man
         #3 = Methodref          #2.#22         // DynamicDispatch$Man."<init>":()V
         #4 = Class              #24            // DynamicDispatch$Woman
         #5 = Methodref          #4.#22         // DynamicDispatch$Woman."<init>":()V
         #6 = Methodref          #12.#25        // DynamicDispatch$Human.sayHello:()V
         #7 = Class              #26            // DynamicDispatch
         #8 = Class              #27            // java/lang/Object
         #9 = Utf8               Woman
        #10 = Utf8               InnerClasses
        #11 = Utf8               Man
        #12 = Class              #28            // DynamicDispatch$Human
        #13 = Utf8               Human
        #14 = Utf8               <init>
        #15 = Utf8               ()V
        #16 = Utf8               Code
        #17 = Utf8               LineNumberTable
        #18 = Utf8               main
        #19 = Utf8               ([Ljava/lang/String;)V
        #20 = Utf8               SourceFile
        #21 = Utf8               DynamicDispatch.java
        #22 = NameAndType        #14:#15        // "<init>":()V
        #23 = Utf8               DynamicDispatch$Man
        #24 = Utf8               DynamicDispatch$Woman
        #25 = NameAndType        #29:#15        // sayHello:()V
        #26 = Utf8               DynamicDispatch
        #27 = Utf8               java/lang/Object
        #28 = Utf8               DynamicDispatch$Human
        #29 = Utf8               sayHello
      {
        public DynamicDispatch();
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=1, locals=1, args_size=1
               0: aload_0
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               4: return
            LineNumberTable:
              line 1: 0
      
        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 DynamicDispatch$Man
               3: dup
               4: invokespecial #3                  // Method DynamicDispatch$Man."<init>":()V
               7: astore_1
               8: new           #4                  // class DynamicDispatch$Woman
              11: dup
              12: invokespecial #5                  // Method DynamicDispatch$Woman."<init>":()V
              15: astore_2
              16: aload_1
              17: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
              20: aload_2
              21: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
              24: new           #4                  // class DynamicDispatch$Woman
              27: dup
              28: invokespecial #5                  // Method DynamicDispatch$Woman."<init>":()V
              31: astore_1
              32: aload_1
              33: invokevirtual #6                  // Method DynamicDispatch$Human.sayHello:()V
              36: return
            LineNumberTable:
              line 22: 0
              line 23: 8
              line 25: 16
              line 26: 20
              line 28: 24
              line 29: 32
              line 30: 36
      }
      SourceFile: "DynamicDispatch.java"
      InnerClasses:
           static #9= #4 of #7; //Woman=class DynamicDispatch$Woman of class DynamicDispatch
           static #11= #2 of #7; //Man=class DynamicDispatch$Man of class DynamicDispatch
           static abstract #13= #12 of #7; //Human=class DynamicDispatch$Human of class DynamicDispatch
      View Code

       

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

       

以上是关于《深入理解Java虚拟机》- 重载与重写的主要内容,如果未能解决你的问题,请参考以下文章

Java 中多态的实现(上)

深入理解Java虚拟机:JVM高级特性与最佳实践的内容简介

深入理解Java虚拟机 类的加载器

深入理解java虚拟机-----java内存区域以及内存溢出异常

深入理解Java虚拟机- 学习笔记 - Java内存模型与线程

《深入理解java虚拟机》-晚期(运行期)优化