zoukankan      html  css  js  c++  java
  • 重载和重写在jvm运行中的区别(一)

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

    基本概念

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

    1 Human man = new Man();
    2 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

    重载只会发生在编译期,即编译器时jvm可以通过静态类型确定符号引用所对应的直接引用。

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

    1 //静态类型变化
    2 sr.sayHello((Man) man);
    3 sr.sayHello((Woman) man);
    4 //实际类型变化
    5 Human man = new Man();
    6 man = new Woman();

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

     1 /**
     2  * Created by fan on 2016/3/28.
     3  */
     4 public class StaticDispatcher {
     5 
     6     static class Human {}
     7     static class Man extends Human {}
     8     static class Woman extends Human {}
     9 
    10     public void sayHello(Human human) {
    11         System.out.println("Hello guy!");
    12     }
    13 
    14     public void sayHello(Man man) {
    15         System.out.println("Hello man!");
    16     }
    17 
    18     public void sayHello(Woman woman) {
    19         System.out.println("Hello woman!");
    20     }
    21 
    22     public static void main(String[] args) {
    23         StaticDispatcher staticDispatcher = new StaticDispatcher();
    24         Human man = new Man();
    25         Human woman = new Woman();
    26         staticDispatcher.sayHello(man);
    27         staticDispatcher.sayHello(woman);
    28         staticDispatcher.sayHello((Man)man);
    29         staticDispatcher.sayHello((Woman)man);
    30     }
    31 }

    先看看执行结果: 

    这里写图片描述

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

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

    重写发生在运行期,在运行时jvm会先判断对象的动态类型,而后根据对象的动态类型选择对应vtable,从而根据符号引用找到对应的直接引用。

    如:

    BaseClass c = new ChildClass();

    则c能访问的函数列表为Method1,Method2,即黄色部分。

    测试代码如下:

     1 /**
     2  * Created by fan on 2016/3/29.
     3  */
     4 public class DynamicDispatcher {
     5 
     6     static abstract class Human {
     7         protected abstract void sayHello();
     8     }
     9 
    10     static class Man extends Human {
    11 
    12         @Override
    13         protected void sayHello() {
    14             System.out.println("Man say hello");
    15         }
    16     }
    17 
    18     static class Woman extends Human {
    19 
    20         @Override
    21         protected void sayHello() {
    22             System.out.println("Woman say hello");
    23         }
    24     }
    25 
    26     public static void main(String[] args) {
    27         Human man = new Man();
    28         Human woman = new Woman();
    29         man.sayHello();
    30         woman.sayHello();
    31         man = new Woman();
    32         man.sayHello();
    33     }
    34 
    35 }

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

    看下字节码指令:

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

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

    (1)测试代码如下:

     1 /**
     2  * Created by fan on 2016/3/29.
     3  */
     4 public class Test {
     5 
     6     static class Human {
     7         protected void sayHello() {
     8             System.out.println("Human say hello");
     9         }
    10         protected void sayHehe() {
    11             System.out.println("Human say hehe");
    12         }
    13     }
    14 
    15     static class Man extends Human {
    16 
    17         @Override
    18         protected void sayHello() {
    19             System.out.println("Man say hello");
    20         }
    21 
    22 //        protected void sayHehe() {
    23 //            System.out.println("Man say hehe");
    24 //        }
    25     }
    26 
    27     static class Woman extends Human {
    28 
    29         @Override
    30         protected void sayHello() {
    31             System.out.println("Woman say hello");
    32         }
    33 
    34 //        protected void sayHehe() {
    35 //            System.out.println("Woman say hehe");
    36 //        }
    37     }
    38 
    39     public static void main(String[] args) {
    40         Human man = new Man();
    41         man.sayHehe();
    42     }
    43 
    44 }

    测试结果如下: 

    这里写图片描述
    字节码指令:

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

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

    (2)测试代码如下:

     1 /**
     2  * Created by fan on 2016/3/29.
     3  */
     4 public class Test {
     5 
     6     static class Human {
     7         protected void sayHello() {
     8         }
     9     }
    10 
    11     static class Man extends Human {
    12 
    13         @Override
    14         protected void sayHello() {
    15             System.out.println("Man say hello");
    16         }
    17 
    18         protected void sayHehe() {
    19             System.out.println("Man say hehe");
    20         }
    21     }
    22 
    23     static class Woman extends Human {
    24 
    25         @Override
    26         protected void sayHello() {
    27             System.out.println("Woman say hello");
    28         }
    29 
    30         protected void sayHehe() {
    31             System.out.println("Woman say hehe");
    32         }
    33     }
    34 
    35     public static void main(String[] args) {
    36         Human man = new Man();
    37         man.sayHehe();
    38     }
    39 
    40 }

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

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

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

     1 /**
     2  * Created by fan on 2016/3/29.
     3  */
     4 public class Test {
     5 
     6     static class Human {
     7         protected void sayHello() {
     8             System.out.println("Human say hello");
     9         }
    10 //        protected void sayHehe() {
    11 //            System.out.println("Human say hehe");
    12 //        }
    13     }
    14 
    15     static class Man extends Human {
    16 
    17         @Override
    18         protected void sayHello() {
    19             System.out.println("Man say hello");
    20         }
    21 
    22         protected void sayHehe() {
    23             System.out.println("Man say hehe");
    24         }
    25     }
    26 
    27     static class Woman extends Human {
    28 
    29         @Override
    30         protected void sayHello() {
    31             System.out.println("Woman say hello");
    32         }
    33 
    34         protected void sayHehe() {
    35             System.out.println("Woman say hehe");
    36         }
    37     }
    38 
    39     public static void main(String[] args) {
    40         Man man = new Man();
    41         man.sayHehe();
    42     }
    43 
    44 }

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

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

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

    结束语

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

    参考资料

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

    转自:http://blog.csdn.net/fan2012huan/article/details/50999777

  • 相关阅读:
    Daemon Tools手工完全卸载方案
    不要轻易删除/windows/install下文件
    Dumpbin命令的使用
    v4l2 视频捕获
    2瓶4两酒,1个1.5两的酒杯
    n个平面分空间最多可分成多少份
    &#65279导致页面顶部空白一行解决方法
    Base64编码原理分析
    浏览器中“JavaScript解析器”工作原理
    IList转化为DataSet,解决了System.nullable()的问题
  • 原文地址:https://www.cnblogs.com/zl1991/p/6904009.html
Copyright © 2011-2022 走看看