标签:style sys 定义 jvm line 运行 学习笔记 .com img
本篇文章从JVM的角度来理解Java学习中经常提到的重载和重写。
方法调用:方法调用不等同于方法执行,在Java虚拟机中,方法调用仅仅是为了确定调用哪个版本的方法。方法调用分为解析调用和分派。解析调用一定是静态的,而分派可以是静态的,也可以是动态的。我们这里只介绍分派中的静态分配和动态分配。
下面看个例子,顺便来猜一下结果(面试中经常遇到):
1 class Human { 2 3 } 4 5 class Man extends Human{ 6 7 } 8 9 class Woman extends Human { 10 11 } 12 13 public class overLoadTest { 14 15 public void sayHello(Human guy){ 16 System.out.println("Hello guy!"); 17 } 18 public void sayHello(Man man){ 19 System.out.println("Hello man!"); 20 } 21 public void sayHello(Woman woman){ 22 System.out.println("Hello woman!"); 23 } 24 25 26 public static void main(String[] args){ 27 Human man = new Man(); 28 Human woman = new Woman(); 29 30 overLoadTest sr = new overLoadTest(); 31 32 sr.sayHello(man); 33 sr.sayHello(woman); 34 35 } 36 }
运行结果如下:
如果不明白为什么是这样的结果,我们先来了解一下,什么是静态类型和实际类型。
Human man = new Man();
Human woman = new Woman();
这里Human为静态类型,man和woman为实际类型。区别为静态类型的变化仅仅在使用时发生,变量本身的静态类型不会改变,并且最终的静态类型是在编译期间可知的;而实际类型变化的结果在运行期间才可确定,编译器在编译程序时并不知道一个对象的实际类型是什么。
静态分配最典型的应用就是方法重载。我们再来复习一下方法重载的定义:在一个类中有多个方法,名称相同但参数列表不同(参数个数,参数类型,参数顺序)。上面例子就是方法重载。这里的运行结果毫不意外。
sr.sayHello(man);
sr.sayHello(woman);
man和woman的静态类型都是Human。
动态分配 :所有依赖实际类型来定位方法执行版本的分派动作称为静态分配。
动态分配的典型应用为重写。下面为重写的例子
1 abstract class Human_Test { 2 public abstract void sayHello(); 3 } 4 5 class Man_Test extends Human_Test{ 6 @Override 7 public void sayHello() { 8 System.out.println("Hello man!"); 9 } 10 11 } 12 13 class Woman_Test extends Human_Test { 14 @Override 15 public void sayHello() { 16 System.out.println("Hello woman!"); 17 } 18 19 } 20 21 public class OverrideTest { 22 public static void main(String[] args){ 23 Human_Test man = new Man_Test(); 24 Human_Test woman = new Woman_Test(); 25 26 man.sayHello(); 27 woman.sayHello(); 28 29 man = new Woman_Test(); 30 man.sayHello(); 31 } 32 33 34 35 }
运行结果:
因为动态分配是根据实际类型来确定要调用的方法的,这样的结果也比较符合我们的思维习惯。那么JVM又是怎么根据实际类型来找到目标方法的呢?用Javap来分析一下字节码:
看上图17和21行,可以看到已经找到了Human中的sayHello(),但是invokevirtual指令是多态查找指令。其过程如下:
1). 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C.
2). 如果在类型C中找到与常量池中描述符和简单名称都相符的方法,则进行访问权限的校验,如果校验不通过,则返回java.lang.IllegaAccessError异常,校验通过则直接返回方法的直接引用,查找过程结束。
3). 否则,按照继承关系从下往上一次对C的各个父类进行第二步骤的搜索和验证过程。
4). 如果始终还是没有找到合适的方法直接引用,则抛出java.lang.AbstractMethodError异常。
由于invokevirtual指令执行的第一步是在运行时确定接收者的实际类型,所以两次中的invokevirtual指令把常量池中的类方法符号引用解析到不同的直接引用上,这个就是java语言中方法重写的本质。
标签:style sys 定义 jvm line 运行 学习笔记 .com img
原文地址:https://www.cnblogs.com/yiRain1992/p/8858543.html