1.重载(overload)方法

对重载方法的调用主要看静态类型,静态类型是什么类型,就调用什么类型的参数方法。

2.重写(override)方法

对重写方法的调用主要看实际类型。实际类型如果实现了该方法则直接调用该方法,如果没有实现,则在继承关系中从低到高搜索有无实现。

3.

java文件的编译过程中不存在传统编译的连接过程,一切方法调用在class文件中存放的只是符号引用,而不是方法在实际运行时内存布局中的入口地址。

基本概念

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

1 Human man = newMan();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 = newMan();6 man = new Woman();

重载只涉及静态类型的选择。

测试代码如下:

1 /**

2 * Created by fan on 2016/3/28.3 */

4 public classStaticDispatcher {5

6 static classHuman {}7 static class Man extendsHuman {}8 static class Woman extendsHuman {}9

10 public voidsayHello(Human human) {11 System.out.println("Hello guy!");12 }13

14 public voidsayHello(Man man) {15 System.out.println("Hello man!");16 }17

18 public voidsayHello(Woman woman) {19 System.out.println("Hello woman!");20 }21

22 public static voidmain(String[] args) {23 StaticDispatcher staticDispatcher = newStaticDispatcher();24 Human man = newMan();25 Human woman = newWoman();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 voidmain(java.lang.String[]);2 Code:3 Stack=2, Locals=4, Args_size=1

4 0: new #7; //class StaticDispatcher

5 3: dup6 4: invokespecial #8; //Method "":()V

7 7: astore_18 8: new #9; //class StaticDispatcher$Man

9 11: dup10 12: invokespecial #10; //Method StaticDispatcher$Man."":()V

11 15: astore_212 16: new #11; //class StaticDispatcher$Woman

13 19: dup14 20: invokespecial #12; //Method StaticDispatcher$Woman."":()V

15 23: astore_316 24: aload_117 25: aload_218 26: invokevirtual #13; //Method sayHello:(LStaticDispatcher$Human;)V

19 29: aload_120 30: aload_321 31: invokevirtual #13; //Method sayHello:(LStaticDispatcher$Human;)V

22 34: aload_123 35: aload_224 36: checkcast #9; //class StaticDispatcher$Man

25 39: invokevirtual #14; //Method sayHello:(LStaticDispatcher$Man;)V

26 42: aload_127 43: aload_228 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 classDynamicDispatcher {5

6 static abstract classHuman {7 protected abstract voidsayHello();8 }9

10 static class Man extendsHuman {11

12 @Override13 protected voidsayHello() {14 System.out.println("Man say hello");15 }16 }17

18 static class Woman extendsHuman {19

20 @Override21 protected voidsayHello() {22 System.out.println("Woman say hello");23 }24 }25

26 public static voidmain(String[] args) {27 Human man = newMan();28 Human woman = newWoman();29 man.sayHello();30 woman.sayHello();31 man = newWoman();32 man.sayHello();33 }34

35 }

执行结果:

看下字节码指令:

1 public static voidmain(java.lang.String[]);2 Code:3 Stack=2, Locals=3, Args_size=1

4 0: new #2; //class DynamicDispatcher$Man

5 3: dup6 4: invokespecial #3; //Method DynamicDispatcher$Man."":()V

7 7: astore_18 8: new #4; //class DynamicDispatcher$Woman

9 11: dup10 12: invokespecial #5; //Method DynamicDispatcher$Woman."":()V

11 15: astore_212 16: aload_113 17: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V

14 20: aload_215 21: invokevirtual #6; //Method DynamicDispatcher$Human.sayHello:()V

16 24: new #4; //class DynamicDispatcher$Woman

17 27: dup18 28: invokespecial #5; //Method DynamicDispatcher$Woman."":()V

19 31: astore_120 32: aload_121 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 classTest {5

6 static classHuman {7 protected voidsayHello() {8 System.out.println("Human say hello");9 }10 protected voidsayHehe() {11 System.out.println("Human say hehe");12 }13 }14

15 static class Man extendsHuman {16

17 @Override18 protected voidsayHello() {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 extendsHuman {28

29 @Override30 protected voidsayHello() {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 voidmain(String[] args) {40 Human man = newMan();41 man.sayHehe();42 }43

44 }

测试结果如下:

字节码指令:

1 public static voidmain(java.lang.String[]);2 Code:3 Stack=2, Locals=2, Args_size=1

4 0: new #2; //class Test$Man

5 3: dup6 4: invokespecial #3; //Method Test$Man."":()V

7 7: astore_18 8: aload_19 9: invokevirtual #4; //Method Test$Human.sayHehe:()V

10 12: return

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

(2)测试代码如下:

1 /**

2 * Created by fan on 2016/3/29.3 */

4 public classTest {5

6 static classHuman {7 protected voidsayHello() {8 }9 }10

11 static class Man extendsHuman {12

13 @Override14 protected voidsayHello() {15 System.out.println("Man say hello");16 }17

18 protected voidsayHehe() {19 System.out.println("Man say hehe");20 }21 }22

23 static class Woman extendsHuman {24

25 @Override26 protected voidsayHello() {27 System.out.println("Woman say hello");28 }29

30 protected voidsayHehe() {31 System.out.println("Woman say hehe");32 }33 }34

35 public static voidmain(String[] args) {36 Human man = newMan();37 man.sayHehe();38 }39

40 }

编译时报错:

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

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

1 /**

2 * Created by fan on 2016/3/29.3 */

4 public classTest {5

6 static classHuman {7 protected voidsayHello() {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 extendsHuman {16

17 @Override18 protected voidsayHello() {19 System.out.println("Man say hello");20 }21

22 protected voidsayHehe() {23 System.out.println("Man say hehe");24 }25 }26

27 static class Woman extendsHuman {28

29 @Override30 protected voidsayHello() {31 System.out.println("Woman say hello");32 }33

34 protected voidsayHehe() {35 System.out.println("Woman say hehe");36 }37 }38

39 public static voidmain(String[] args) {40 Man man = newMan();41 man.sayHehe();42 }43

44 }

注意在Main方法中,改成了Man man = new Man();

执行结果如下所示:

字节码指令如下所示:

1 public static voidmain(java.lang.String[]);2 Code:3 Stack=2, Locals=2, Args_size=1

4 0: new #2; //class Test$Man

5 3: dup6 4: invokespecial #3; //Method Test$Man."":()V

7 7: astore_18 8: aload_19 9: invokevirtual #4; //Method Test$Man.sayHehe:()V

10 12: return

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

结束语

参考资料

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

java方法重载和重写在jvm_重载和重写在jvm运行中的区别(一)相关推荐

  1. java方法后面throw的作用_Java异常处理中throw与throws的用法区别

    前言 说到异常,我们当然会想到try catch finally.当然如果用这个的话,被try块包围的代码说明这段代码可能会发生异常,一旦发生异常,异常便会被catch捕获到,然后需要在catch块中 ...

  2. java钟使用dwr_DWR中各种java方法的调用

    DWR是一个框架,简单的说就是能够在javascript直接调用java方法,而不必去写一大堆的javascript代码.它的实现是基于ajax的,可以实现无刷新效果. 网上有不少DWR的例子,但大都 ...

  3. Java方法完整调用链生成工具

    1. 前言 在很多场景下,如果能够生成Java代码中方法之间的调用链,是很有帮助的,例如分析代码执行流程.确认被修改代码的影响范围.代码审计/漏洞分析等. IDEA提供了显示调用指定Java方法向上的 ...

  4. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  5. Java 方法重载 方法重写

    方法重载规则 参数个数不同 参数个数相同,但参数列表中对应的某个参数的类型不一样 方法的返回类型和参数名称不参与重载 "编译期绑定",,因为未产生对象,只看参数.引用类型绑定方法 ...

  6. java方法重载_在Python中该如何实现Java的重写与重载

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:清风python PS:如有需要Python学习资料的小伙伴可以加点击 ...

  7. Java 方法重写与重载的区别 示例 重载多数相加求和 重写toString()和equals()

    Java 方法重写与重载的区别 重载:在同一个类中,当方法名相同,形参列表不同的时候 多个方法构成了重载 重写:在不同的类中,子类对父类提供的方法不满意的时候,要对父类的方法进行重写. 名称\属性 E ...

  8. Java方法重写与重载

    目录 一.方法重载(overload) 概念 注意 二.方法重写(0verride) 概念 方法重写的应用场景 @Override注解 注意 三.重写与重载区别 访问修饰符 一.方法重载(overlo ...

  9. Java中方法的重写(overriding)和重载(overloading)的区别

    Java中方法的重写(overriding)和重载(overloading) 重写 在子类中可以根据需要对从父类中继承来的方法进行改造,也称为重置.覆盖. 在子类中创建一个和父类中的一个相同名字.相同 ...

最新文章

  1. 微信安卓版8.0.18内测更新 增加个人信息收集清单
  2. 【渝粤教育】国家开放大学2018年春季 7392-22DMatlab语言及其应用 参考试题
  3. 身份证合法验证查询易语言代码
  4. 【3D文件格式解析】.obj + .mtl
  5. 云盘上传一直显示服务器出错_百度云盘一直服务器忙 百度网盘出现服务器错误...
  6. 基于Hough变换的直线检测(Matlab)
  7. 在ARM板上移植CH341驱动
  8. 电阻电容串联并联关系,一辈子受益
  9. 最小二乘法拟合空间直线的原理及实现
  10. 各层电子数排布规则_电子数的排布规律
  11. 征途服务器 文件夹需手,手游新征途手工架设服务端+配套双客户端+架设流程+服务器部署文档等...
  12. 截流式合流制设计流量计算_截流式合流制管道系统的设计说明
  13. card样式 layui_layui后台模板
  14. uniapp自定义导航栏,手机顶部通知栏字体颜色修改
  15. 体育类素质赛道升温,迎来转型时机
  16. 玛雅云渲染计算机设置,Maya如何使用云渲染,怎么操作?
  17. Unity-编辑器改成中文
  18. DRAMA QUEEN_洪晃在ilook的BLOG
  19. google aab超过150M大小如何优化上传谷歌商店
  20. 若依RuoYi框架浅析——导读

热门文章

  1. 再见!Kafka决定弃用Zookeeper...
  2. 36 张图梳理 Intellij IDEA 常用设置
  3. 7年,从“游戏少年”到大厂技术总监的逆袭之路
  4. 某女程序员吐槽自己被男同事集体排挤!一个人吃饭,一个人敲代码!深深感觉到世界的恶意!...
  5. 家人介绍的91年程序员月薪15k,已秃头!女孩不乐意,妈妈却说往后你只会越找越差!...
  6. 2018,送给大家一份提升技术的宝典
  7. 全栈技术实践经历告诉你:开发一个商城小程序要多少钱?
  8. UniApp H5 浏览器下载图片 兼容iOS、Android
  9. Ubuntu18.04 网络配置
  10. 控制~Control System 线性系统