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

基本概念

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

 Human man = new Man();
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可以通过静态类型确定符号引用所对应的直接引用。

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

 //静态类型变化
sr.sayHello((Man) man);
sr.sayHello((Woman) man);
//实际类型变化
Human man = new Man();
man = new Woman();

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

 /**
* Created by fan on 2016/3/28.
*/
public class StaticDispatcher { static class Human {}
static class Man extends Human {}
static class Woman extends Human {} public void sayHello(Human human) {
System.out.println("Hello guy!");
} public void sayHello(Man man) {
System.out.println("Hello man!");
} public void sayHello(Woman woman) {
System.out.println("Hello woman!");
} public static void main(String[] args) {
StaticDispatcher staticDispatcher = new StaticDispatcher();
Human man = new Man();
Human woman = new Woman();
staticDispatcher.sayHello(man);
staticDispatcher.sayHello(woman);
staticDispatcher.sayHello((Man)man);
staticDispatcher.sayHello((Woman)man);
}
}

先看看执行结果:

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

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

测试代码如下:

 /**
* Created by fan on 2016/3/29.
*/
public class DynamicDispatcher { static abstract class Human {
protected abstract void sayHello();
} static class Man extends Human { @Override
protected void sayHello() {
System.out.println("Man say hello");
}
} static class Woman extends Human { @Override
protected void sayHello() {
System.out.println("Woman say hello");
}
} public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
} }

执行结果: 

看下字节码指令:

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

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

(1)测试代码如下:

 /**
* Created by fan on 2016/3/29.
*/
public class Test { static class Human {
protected void sayHello() {
System.out.println("Human say hello");
}
protected void sayHehe() {
System.out.println("Human say hehe");
}
} static class Man extends Human { @Override
protected void sayHello() {
System.out.println("Man say hello");
} // protected void sayHehe() {
// System.out.println("Man say hehe");
// }
} static class Woman extends Human { @Override
protected void sayHello() {
System.out.println("Woman say hello");
} // protected void sayHehe() {
// System.out.println("Woman say hehe");
// }
} public static void main(String[] args) {
Human man = new Man();
man.sayHehe();
} }

测试结果如下:


字节码指令:

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

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

(2)测试代码如下:

 /**
* Created by fan on 2016/3/29.
*/
public class Test { static class Human {
protected void sayHello() {
}
} static class Man extends Human { @Override
protected void sayHello() {
System.out.println("Man say hello");
} protected void sayHehe() {
System.out.println("Man say hehe");
}
} static class Woman extends Human { @Override
protected void sayHello() {
System.out.println("Woman say hello");
} protected void sayHehe() {
System.out.println("Woman say hehe");
}
} public static void main(String[] args) {
Human man = new Man();
man.sayHehe();
} }

编译时报错: 

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

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

 /**
* Created by fan on 2016/3/29.
*/
public class Test { static class Human {
protected void sayHello() {
System.out.println("Human say hello");
}
// protected void sayHehe() {
// System.out.println("Human say hehe");
// }
} static class Man extends Human { @Override
protected void sayHello() {
System.out.println("Man say hello");
} protected void sayHehe() {
System.out.println("Man say hehe");
}
} static class Woman extends Human { @Override
protected void sayHello() {
System.out.println("Woman say hello");
} protected void sayHehe() {
System.out.println("Woman say hehe");
}
} public static void main(String[] args) {
Man man = new Man();
man.sayHehe();
} }

注意在Main方法中,改成了Man man = new Man(); 
执行结果如下所示: 

字节码指令如下所示:

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

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

结束语

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

参考资料

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

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

最新文章

  1. 解决Eclipse Failed to write core dump. Minidumps are not enabled by default on client versions
  2. Unity3D研究院之使用Animation编辑器编辑动画
  3. Form_Form Builder编译fmb/library/menu方式总结(汇总)
  4. R-S触发器
  5. QT 按钮类继承处理带定时器
  6. 分类图 Class Diagram
  7. hdu 4720
  8. Android上使用OpenGLES2.0显示YUV数据
  9. 怎样在 Swift 项目中使用 CocoaPods
  10. S3C6410嵌入式应用平台构建(一)
  11. 打开asp出现An error occurred on the server when processing the URL
  12. python:os.path
  13. img如果没有图片显示默认图片效果
  14. javaWeb学习之Listener监听
  15. Castle Windsor 的动态代理类如何获取实际类型
  16. SQL中GROUP BY用法示例(转)
  17. javascript常用工具类整理(copy)
  18. javaweb开发1.环境配置(javaweb插件下载及tomact在eclips中配置)
  19. 解决spring-boot启动异常Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean
  20. 【openjudge】【前缀和】P6731啤酒厂选址

热门文章

  1. 【转】NIO的定义和原理是什么?
  2. WebSocket 学习笔记
  3. adb 调试真机 wait for device 错误解决办法
  4. windows环境下Robot Framework的安装步骤
  5. API对接中经常会出现的签名获取,这只是某一种,仅供给有需要的人参考
  6. 树梅派 -- 通过/sys读写ADC芯片 pcf8591
  7. 官网Android离线文档下载
  8. 基于 WPF + Modern UI 的 公司OA小助手 开发总结
  9. 收集Windows 8 Metro UI 风格网站资源,觉得不错的顶啊!!
  10. (4)主成分分析Principal Component Analysis——PCA