java方法调用之重载、重写的调用原理

前一段时间看了《深入理解JVM》第三部分虚拟机执行子系统的内容,看到了重载与重写在JVM层面的调用原理(详见8.3 方法调用一节),但是没有写成博客总结一下,这里讨论讨论。在讨论过程中,难免会涉及到 字节码指令 相关的内容,这部分内容请查看博文: 由常量池 运行时常量池 String intern方法想到的(二)之class文件及字节码指令

结论

1.重载(overload)方法
对重载方法的调用主要看静态类型,静态类型是什么类型,就调用什么类型的参数方法。
2.重写(override)方法
对重写方法的调用主要看实际类型。实际类型如果实现了该方法则直接调用该方法,如果没有实现,则在继承关系中从低到高搜索有无实现。
3.
java文件的编译过程中不存在传统编译的连接过程,一切方法调用在class文件中存放的只是符号引用,而不是方法在实际运行时内存布局中的入口地址。

基本概念

1.静态类型与实际类型,方法接收者

Human man = new Man();
man.foo();

上面这条语句中,man的静态类型为Human,实际类型为Man。所谓方法接收者,就是指将要执行foo()方法的所有者(在多态中,有可能是父类Human的对象,也可能是子类Man的对象)。
2.字节码的方法调用指令
(1)invokestatic:调用静态方法
(2)invokespecial:调用实例构造器方法,私有方法和父类方法。
(3)invokevirtual:调用所有的虚方法。
(4)invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
(5)invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法。
前2条指令(invokestatic, invokespecial),在类加载时就能把符号引用解析为直接引用,符合这个条件的有静态方法、实例构造器方法、私有方法、父类方法这4类,这4类方法叫非虚方法。
非虚方法除了上面静态方法、实例构造器方法、私有方法、父类方法这4种方法之外,还包括final方法。虽然final方法使用invokevirtual指令来调用,但是final方法无法被覆盖,没有其他版本,无需对方法接收者进行多态选择,或者说多态选择的结果是唯一的。

重载overload

上面说的静态类型和动态类型都是可以变化的。静态类型发生变化(强制类型转换)时,对于编译器是可知的,即编译器知道对象的最终静态类型。而实际类型变化(对象指向了其他对象)时,编译器是不可知的,只有在运行时才可知。

//静态类型变化
sr.sayHello((Man) man);
sr.sayHello((Woman) man);
//实际类型变化
Human man = new Man();
man = new Woman();

    重载只涉及静态类型的选择。
    测试代码如下:

    /**
     * Created by fan on 2016/3/28.
     */
    public class StaticDispatcher {
    
        static class Human {}
        static class Man extends Human {}
        static class Woman extends Human {}
    
        public void sayHello(Human human) {
            System.out.println("Hello guy!");
        }
    
        public void sayHello(Man man) {
            System.out.println("Hello man!");
        }
    
        public void sayHello(Woman woman) {
            System.out.println("Hello woman!");
        }
    
        public static void main(String[] args) {
            StaticDispatcher staticDispatcher = new StaticDispatcher();
            Human man = new Man();
            Human woman = new Woman();
            staticDispatcher.sayHello(man);
            staticDispatcher.sayHello(woman);
            staticDispatcher.sayHello((Man)man);
            staticDispatcher.sayHello((Woman)man);
        }
    }

    先看看执行结果:
    这里写图片描述

    由此可见,当静态类型发生变化时,会调用相应类型的方法。但是,当将Man强制类型转换成Woman时,没有编译错误,却有运行时异常。“classCastException”类映射异常。
    看看字节码指令:
    javap -verbose -c StaticDispatcher

    public static void main(java.lang.String[]);
      Code:
       Stack=2, Locals=4, Args_size=1
       0:   new     #7; //class StaticDispatcher
       3:   dup
       4:   invokespecial   #8; //Method "<init>":()V
       7:   astore_1
       8:   new     #9; //class StaticDispatcher$Man
       11:  dup
       12:  invokespecial   #10; //Method StaticDispatcher$Man."<init>":()V
       15:  astore_2
       16:  new     #11; //class StaticDispatcher$Woman
       19:  dup
       20:  invokespecial   #12; //Method StaticDispatcher$Woman."<init>":()V
       23:  astore_3
       24:  aload_1
       25:  aload_2
       26:  invokevirtual   #13; //Method sayHello:(LStaticDispatcher$Human;)V
       29:  aload_1
       30:  aload_3
       31:  invokevirtual   #13; //Method sayHello:(LStaticDispatcher$Human;)V
       34:  aload_1
       35:  aload_2
       36:  checkcast       #9; //class StaticDispatcher$Man
       39:  invokevirtual   #14; //Method sayHello:(LStaticDispatcher$Man;)V
       42:  aload_1
       43:  aload_2
       44:  checkcast       #11; //class StaticDispatcher$Woman
       47:  invokevirtual   #15; //Method sayHello:(LStaticDispatcher$Woman;)V
       50:  return

      看到,在强制类型转换时,会有指令checkCast的调用,而且invokevirtual指令的调用方法也发生了变化39: invokevirtual #14; //Method sayHello:(LStaticDispatcher$Man;)V
      虚拟机(准确说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的。
      对于字面量类型,编译器会自动进行类型转换。转换的顺序为:
      char-int-long-float-double-Character-Serializable-Object
      转换成Character是因为发生了自动装箱,转换成Serializable是因为Character实现了Serializable接口。

      重写override

      测试代码如下:

      /**
       * Created by fan on 2016/3/29.
       */
      public class DynamicDispatcher {
      
          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();
          }
      
      }

      执行结果:
      这里写图片描述

      看下字节码指令:

      public static void main(java.lang.String[]);
        Code:
         Stack=2, Locals=3, Args_size=1
         0:   new     #2; //class DynamicDispatcher$Man
         3:   dup
         4:   invokespecial   #3; //Method DynamicDispatcher$Man."<init>":()V
         7:   astore_1
         8:   new     #4; //class DynamicDispatcher$Woman
         11:  dup
         12:  invokespecial   #5; //Method DynamicDispatcher$Woman."<init>":()V
         15:  astore_2
         16:  aload_1
         17:  invokevirtual   #6; //Method DynamicDispatcher$Human.sayHello:()V
         20:  aload_2
         21:  invokevirtual   #6; //Method DynamicDispatcher$Human.sayHello:()V
         24:  new     #4; //class DynamicDispatcher$Woman
         27:  dup
         28:  invokespecial   #5; //Method DynamicDispatcher$Woman."<init>":()V
         31:  astore_1
         32:  aload_1
         33:  invokevirtual   #6; //Method DynamicDispatcher$Human.sayHello:()V
         36:  return

      从字节码中可以看到,他们调用的都是相同的方法invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V ,但是执行的结果却显示调用了不同的方法。因为,在编译阶段,编译器只知道对象的静态类型,而不知道实际类型,所以在class文件中只能确定要调用父类的方法。但是在执行时却会判断对象的实际类型。如果实际类型实现这个方法,则直接调用,如果没有实现,则按照继承关系从下往上一次检索,只要检索到就调用,如果始终没有检索到,则抛异常(难道能编译通过)。

      (1)测试代码如下:

      /**
       * Created by fan on 2016/3/29.
       */
      public class Test {
      
          static class Human {
              protected void sayHello() {
                  System.out.println("Human say hello");
              }
              protected void sayHehe() {
                  System.out.println("Human say hehe");
              }
          }
      
          static class Man extends Human {
      
              @Override
              protected void sayHello() {
                  System.out.println("Man say hello");
              }
      
      //        protected void sayHehe() {
      //            System.out.println("Man say hehe");
      //        }
          }
      
          static class Woman extends Human {
      
              @Override
              protected void sayHello() {
                  System.out.println("Woman say hello");
              }
      
      //        protected void sayHehe() {
      //            System.out.println("Woman say hehe");
      //        }
          }
      
          public static void main(String[] args) {
              Human man = new Man();
              man.sayHehe();
          }
      
      }

      测试结果如下:
      这里写图片描述
      字节码指令:

      public static void main(java.lang.String[]);
        Code:
         Stack=2, Locals=2, Args_size=1
         0:   new     #2; //class Test$Man
         3:   dup
         4:   invokespecial   #3; //Method Test$Man."<init>":()V
         7:   astore_1
         8:   aload_1
         9:   invokevirtual   #4; //Method Test$Human.sayHehe:()V
         12:  return

      字节码指令与上面代码的字节码指令没有本质区别。

      (2)测试代码如下:

      /**
       * Created by fan on 2016/3/29.
       */
      public class Test {
      
          static class Human {
              protected void sayHello() {
              }
          }
      
          static class Man extends Human {
      
              @Override
              protected void sayHello() {
                  System.out.println("Man say hello");
              }
      
              protected void sayHehe() {
                  System.out.println("Man say hehe");
              }
          }
      
          static class Woman extends Human {
      
              @Override
              protected void sayHello() {
                  System.out.println("Woman say hello");
              }
      
              protected void sayHehe() {
                  System.out.println("Woman say hehe");
              }
          }
      
          public static void main(String[] args) {
              Human man = new Man();
              man.sayHehe();
          }
      
      }

      编译时报错:
      这里写图片描述

      这个例子说明了:Java编译器是基于静态类型进行检查的。

      修改上面错误代码,如下所示:

      /**
       * Created by fan on 2016/3/29.
       */
      public class Test {
      
          static class Human {
              protected void sayHello() {
                  System.out.println("Human say hello");
              }
      //        protected void sayHehe() {
      //            System.out.println("Human say hehe");
      //        }
          }
      
          static class Man extends Human {
      
              @Override
              protected void sayHello() {
                  System.out.println("Man say hello");
              }
      
              protected void sayHehe() {
                  System.out.println("Man say hehe");
              }
          }
      
          static class Woman extends Human {
      
              @Override
              protected void sayHello() {
                  System.out.println("Woman say hello");
              }
      
              protected void sayHehe() {
                  System.out.println("Woman say hehe");
              }
          }
      
          public static void main(String[] args) {
              Man man = new Man();
              man.sayHehe();
          }
      
      }

      注意在Main方法中,改成了Man man = new Man();
      执行结果如下所示:
      这里写图片描述
      字节码指令如下所示:

      public static void main(java.lang.String[]);
        Code:
         Stack=2, Locals=2, Args_size=1
         0:   new     #2; //class Test$Man
         3:   dup
         4:   invokespecial   #3; //Method Test$Man."<init>":()V
         7:   astore_1
         8:   aload_1
         9:   invokevirtual   #4; //Method Test$Man.sayHehe:()V
         12:  return

      注意上面的字节码指令invokevirtual #4; //Method Test$Man.sayHehe:()V

      结束语

      本文讨论了一下重载与重写的基本原理,查看了相关的字节码指令,下篇博文 java方法调用之单分派与多分派(二)讨论下单分派与多分派。
      可以再看看这篇博文 java方法调用之动态调用多态(重写override)的实现原理——方法表(三)

      参考资料

      • 周志明 《深入理解JAVA虚拟机》

      转载:https://blog.csdn.net/fan2012huan/article/details/50999777

      發表評論
      所有評論
      還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
      相關文章