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

  • 相关阅读:
    06 is和==的区别 encode()编码 decode()解码
    05 dic的增删改查 字典的嵌套 考试题dic.get()的相关使用
    03 编码 int ,bool,str的常用操作 主要讲str
    01 基本数据类型 变量 if语句
    04 列表的增删改查 常用方法 元祖 range
    02 while循环 格式化输出 运算符
    多校2 Harmonious Army hdu6598 网络流
    P3159 [CQOI2012]交换棋子 网络流
    P2172 [国家集训队]部落战争 最大流
    P2402 奶牛隐藏 网络流
  • 原文地址:https://www.cnblogs.com/zl1991/p/6904009.html
Copyright © 2011-2022 走看看