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

重载和重写在jvm运行中的区别(一)的更多相关文章

  1. c++继承关系中成员函数的重载、重写、重定义之间的区别

    1.Override.Overload.Redefine Overload 重载只能发生在类内部,不能发生在子类和父类的继承中.具体来说,如果子类中有父类同名.同返回值类型,但是不同参数列表,这两个在 ...

  2. Java_重载与重写

    在java中,重载与重写都是多态的体现.重载(Overload)体现的是编译时多态,而重写(Override)体现了运行时多态. 重载(Overload): 定义:在一个类中,同名的方法如果有不同的参 ...

  3. [转]Java中继承、多态、重载和重写介绍

    什么是多态?它的实现机制是什么呢?重载和重写的区别在那里?这就是这一次我们要回顾的四个十分重要的概念:继承.多态.重载和重写. 继承(inheritance) 简单的说,继承就是在一个现有类型的基础上 ...

  4. 从JVM字节码执行看重载和重写

    Java 重写(Override)与重载(Overload) 重写(Override) 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变.即外壳不变,核心重写! 重写的 ...

  5. 从字节码指令看重写在JVM中的实现

    Java是解释执行的.包含动态链接的特性.都给解析或执行期间提供了非常多灵活扩展的空间.面向对象语言的继承.封装和多态的特性,在JVM中是怎样进行编译.解析,以及通过字节码指令怎样确定方法调用的版本号 ...

  6. Java中重载和重写的区别

    重载 overloading 1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型.重载是一个类中多态性的一种表现. 2) Java的方法重载,就 ...

  7. C++中重载、重写(覆盖)和隐藏的区别实例分析

    这篇文章主要介绍了C++中重载.重写(覆盖)和隐藏的区别,是C++面向对象程序设计非常重要的概念,需要的朋友可以参考下 本文实例讲述了C++中重载.重写(覆盖)和隐藏的区别,对于C++面向对象程序设计 ...

  8. C++类中的一些细节(重载、重写、覆盖、隐藏,构造函数、析构函数、拷贝构造函数、赋值函数在继承时的一些问题)

    1 函数的重载.重写(重定义).函数覆盖及隐藏 其实函数重载与函数重写.函数覆盖和函数隐藏不是一个层面上的概念.前者是同一个类内,或者同一个函数作用域内,同名不同参数列表的函数之间的关系.而后三者是基 ...

  9. 描述 Java 中的重载和重写?

    重载和重写都允许你用相同的名称来实现不同的功能,但是重载是编译时活动, 而重写是运行时活动.你可以在同一个类中重载方法,但是只能在子类中重写方 法.重写必须要有继承.

随机推荐

  1. AspNetCore容器化(Docker)部署(二) —— 多容器通信

    一.前言 着上一篇 AspNetCore容器化(Docker)部署(一) —— 入门,在单个容器helloworld的基础上引入nginx反向代理服务器组成多容器应用. 二.配置反向代理转接 配置转接 ...

  2. 基于纯注解的spring开发的介绍

    几个核心注解的介绍1.@Configuration它的作用是:将一个java类修饰为==配置文件==,在这个java类进行组件注册1package com.kkb.config; import org ...

  3. JavaScript设计模式基础之this、call、apply

    1.this的指向 除去不常用的with和eval,具体应用中this指向大概能分为4种情况分别是 1.作为对象的方法调用. 2.作为普通函数的方法调用. 3.Function.prototype.c ...

  4. 整理几个牛人博客以及OJ

    Blogs 陈立杰(wjmzbmr):http://wjmzbmr.com/ 飘过的小牛:http://blog.csdn.net/niushuai666 王垠:http://www.yinwang. ...

  5. Springboot整合Shiro安全框架

    最近在学习Springboot,在这个过程中遇到了很多之前都没有技术知识,学习了一阵子,稍微总结一些. ---- Shiro框架 shiro框架,是一个相对比较简便的安全框架,它可以干净利落地处理身份 ...

  6. kvm使用kickstart文件自动安装系统

    假定kvm已经准备好 1.创建磁盘 qemu-img create -f qcow2 /kvm/os/vm-01.qcow2 16G 2.上传或下载安装镜像 mkdir -p /kvm/iso cd ...

  7. 大页(Huge Page)简单介绍

    x86(包括x86-32和x86-64)架构的CPU默认使用4KB大小的内存页面(getconf PAGESIZE),但是它们也支持较大的内存页,如x86-64系统就支持2MB大小的大页(huge p ...

  8. python 容器 用户注册登录系统

    1. 列表和普通变量有什么区别 列表是数据类型,普通变量是用来存储数据的 可以把列表赋值给普通变量 2.存在列表 a = [11, 22, 33], 如何向列表中添加(增)新元素 44 a.appen ...

  9. RabbitMQ 关键词解释

    源地址: https://www.cnblogs.com/hz04022016/p/6518138.html RabbitMQ是流行的开源消息队列系统,用erlang语言开发.RabbitMQ是AMQ ...

  10. 一个关于vue+mysql+express的全栈项目(五)------ 实时聊天部分socket.io

    一.基于web端的实时通讯,我们都知道有websocket,为了快速开发,本项目我们采用socket.io(客户端使用socket.io-client) Socket.io是一个WebSocket库, ...