博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重载和重写在jvm运行中的区别(一)
阅读量:4552 次
发布时间:2019-06-08

本文共 9586 字,大约阅读时间需要 31 分钟。

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 "
":()V 7 7: astore_1 8 8: new #9; //class StaticDispatcher$Man 9 11: dup10 12: invokespecial #10; //Method StaticDispatcher$Man."
":()V11 15: astore_212 16: new #11; //class StaticDispatcher$Woman13 19: dup14 20: invokespecial #12; //Method StaticDispatcher$Woman."
":()V15 23: astore_316 24: aload_117 25: aload_218 26: invokevirtual #13; //Method sayHello:(LStaticDispatcher$Human;)V19 29: aload_120 30: aload_321 31: invokevirtual #13; //Method sayHello:(LStaticDispatcher$Human;)V22 34: aload_123 35: aload_224 36: checkcast #9; //class StaticDispatcher$Man25 39: invokevirtual #14; //Method sayHello:(LStaticDispatcher$Man;)V26 42: aload_127 43: aload_228 44: checkcast #11; //class StaticDispatcher$Woman29 47: invokevirtual #15; //Method sayHello:(LStaticDispatcher$Woman;)V30 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         @Override13         protected void sayHello() {14             System.out.println("Man say hello");15         }16     }17 18     static class Woman extends Human {19 20         @Override21         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."
":()V 7 7: astore_1 8 8: new #4; //class DynamicDispatcher$Woman 9 11: dup10 12: invokespecial #5; //Method DynamicDispatcher$Woman."
":()V11 15: astore_212 16: aload_113 17: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V14 20: aload_215 21: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V16 24: new #4; //class DynamicDispatcher$Woman17 27: dup18 28: invokespecial #5; //Method DynamicDispatcher$Woman."
":()V19 31: astore_120 32: aload_121 33: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V22 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         @Override18         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         @Override30         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."
":()V 7 7: astore_1 8 8: aload_1 9 9: invokevirtual #4; //Method Test$Human.sayHehe:()V10 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         @Override14         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         @Override26         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         @Override18         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         @Override30         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."
":()V 7 7: astore_1 8 8: aload_1 9 9: invokevirtual #4; //Method Test$Man.sayHehe:()V10 12: return

 

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

结束语

本文讨论了一下重载与重写的基本原理,查看了相关的字节码指令,下篇博文讨论下单分派与多分派。 

可以再看看这篇博文 

参考资料

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

 

转自:

转载于:https://www.cnblogs.com/zl1991/p/6904009.html

你可能感兴趣的文章
Python 定义自己的常量类
查看>>
C++读取文本文件
查看>>
Python 字典排序
查看>>
sql中写标量函数生成大写拼音首字母
查看>>
ASP.NET Core 2.1 : 十五.图解路由(2.1 or earler)
查看>>
服务器返回状态码说明
查看>>
GitHub for Windows提交失败“failed to sync this branch”
查看>>
linux 安装 git
查看>>
Margin
查看>>
完成登录与注册页面的前端
查看>>
centos 源码安装php7
查看>>
Log4j详细教程
查看>>
UVa-1368-DNA序列
查看>>
ConfigParser模块
查看>>
如何开发优质的 Flutter App:Flutter App 软件测试指南
查看>>
决胜Flutter 第一章 熟悉战场
查看>>
如何开发优质的 Flutter App:Flutter App 软件调试指南
查看>>
决胜经典算法之冒泡排序
查看>>
决胜经典算法之选择排序
查看>>
11、求二进制中1的个数
查看>>