静态分派与动态分派

静态类型,即是变量声明时的类型

实际类型,变量实例化时采用的类型

静态分派

 1 public class StaticDispatch {  
 2     static abstract class Human{  
 3     }  
 4     static class Man extends Human{  
 5     }  
 6     static class Woman extends Human{  
 7     }  
 8     public static void sayHello(Human guy){  
 9         System.out.println("hello,guy!");  
10     }  
11     public static void sayHello(Man guy){  
12         System.out.println("hello,gentlemen!");  
13     }  
14     public static void sayHello(Woman guy){  
15         System.out.println("hello,lady!");  
16     }  
17       
18     public static void main(String[] args) {  
19         Human man=new Man();  
20         Human woman=new Woman();  
21         sayHello(man);  
22         sayHello(woman);  
23     }  
24 }  

  输出: hello,guy!

  hello,guy!

  Human man=new Man();

  我们把“Human”称为变量的静态类型,后面的“Man”称为变量的实际类型

  编译器在编译期并不知道一个对象的实际类型是什么

  编译器在重载时是通过参数的静态类型而不是实际类型作为判定的依据。

  并且静态类型在编译期可知,因此,编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本。

  所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(根据参数的静态类型来定位目标方法)。

  静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。

动态分派

 1 public class DynamicDispatch {  
 2     static abstract class Human{  
 3         protected abstract void sayHello();  
 4     }  
 5     static class Man extends Human{   
 6         @Override  
 7         protected void sayHello() {   
 8             System.out.println("man say hello!");  
 9         }  
10     }  
11     static class Woman extends Human{   
12         @Override  
13         protected void sayHello() {   
14             System.out.println("woman say hello!");  
15         }  
16     }   
17     public static void main(String[] args) {  
18           
19         Human man=new Man();  
20         Human woman=new Woman();  
21         man.sayHello();  
22         woman.sayHello();  
23         man=new Woman();  
24         man.sayHello();   
25     }  
26 }  

输出:
man say hello!
woman say hello!
woman say hello!

显然,这里不可能再根据静态类型来决定,因为静态类型同样是Human的两个变量man和woman在调用sayHello()方法时执行了不同的行为,并且变量man在两次调用中执行了不同的方法。导致这个现象的原因很明显,是这两个变量的实际类型不同,Java虚拟机是如何根据实际类型来分派方法执行版本的呢?
我们从invokevirtual指令的多态查找过程开始说起,invokevirtual指令的运行时解析过程大致分为以下几个步骤:

1、找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
2、如果在类型C中找到与常量中的描述符和简单名称相符合的方法,然后进行访问权限验证,如果验证通过则返回这个方法的直接引用,查找过程结束;如果验证不通过,则抛出java.lang.IllegalAccessError异常。
3、否则未找到,就按照继承关系从下往上依次对类型C的各个父类进行第2步的搜索和验证过程。
4、如果始终没有找到合适的方法,则跑出java.lang.AbstractMethodError异常。

由于invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同直接引用上,这个过程就是Java语言方法重写的本质。

我们把这种在运行期根据实际类型确定方法执行版本的分派过程称为动态分派

java语言是一门静态多分派,动态单分派的语言

原文地址:https://www.cnblogs.com/mengchunchen/p/7860397.html