静态分派和动态分派
首先是两个概念:
- 静态类型,即是变量声明时的类型
- 实际类型,变量实例化时采用的类型
比如我们有这样一段代码
class Human {}
public class Man extends Human {
public static void main(String[] args) {
Human man = new Man();
}
}
我们就称变量 man 的静态类型为 Human,实际类型为 Man。
静态分派
在编译期所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,其典型应用是方法重载(根据参数的静态类型来定位目标方法)。
静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机执行的。静态类型在编译期是可知的;
动态分派
在运行期根据实际类型确定方法执行版本的分派动作称为动态分派,其典型应用是方法重写。
本文以Java多态的一些基本特征来谈一下分派调用。
在开始,依旧用常用方式,例子来引入,看一看下面例子的输出:
/** * Created by Administrator on 2018/3/20 0020. */ public class StaticDispatch { public void sayHello(Human guy) { System.out.println("hello,guy !"); } public void sayHello(Man guy) { System.out.println("hello, man !"); } public void sayHello(Women guy) { System.out.println("hello, women !"); } public static void main(String[] args) { Human man = new Man(); Human women = new Women(); StaticDispatch sd = new StaticDispatch(); sd.sayHello(man); sd.sayHello(women); } } class Human { } class Man extends Human { } class Women extends Human { }
输出结果:
hello, guy!
hello, guy!没错,程序就是大家熟悉的重载(Overload),而且大家也应该能知道输出结果,但是为什么输出结果会是这个呢?
先来谈一下以下代码的定义:
Human man = new Man();
我们把Human称为变量的静态类型,Man称为变量的实际类型。
其中,变量的静态类型和动态类型在程序中都可以发生变化,而区别是变量的静态类型是在编译阶段就可知的,但是动态类型要在运行期才可以确定,编译器在编译的时候并不知道变量的实际类型是什么(个人认为可能也是因为要实现多态,所以才会这样设定)。
现在回到代码中,由于方法的接受者已经确定是StaticDispatch的实例sd了,所以最终调用的是哪个重载版本也就取决于传入参数的类型了。
实际上,虚拟机(应该说是编译器)在重载时时通过参数的静态类型来当判定依据的,而且静态类型在编译期可知,所以编译器在编译阶段就可根据静态类型来判定究竟使用哪个重载版本。于是对于例子中的两个方法的调用都是以Human为参数的版本。
Java中,所有以静态类型来定位方法执行版本的分派动作,都称为静态分派。
再来看动态分派,它和多态的另外一个重要体现有很大的关联,这个体现是什么,可能大家也能猜出,没错,就是重写(override)。
例子如下:
public class DynamicDispatch { public static void main(String[] args) { Human man = new Man(); Human women = new Women(); man.sayHello(); women.sayHello(); man = new Women(); man.sayHello(); } } abstract class Human { protected abstract void sayHello(); } class Man extends Human { @Override protected void sayHello() { System.out.println("hello man!"); } } class Women extends Human { @Override protected void sayHello() { System.out.println("hello women!"); } }
输出结果:
hello man!
hello women!
hello women!这个结果已经没什么好说的了,而虚拟机是如何知道要调用哪个方法的呢?
其实由两次改变man变量的实际类型导致调用函数版本不同,我们就可以知道,虚拟机是根据变量的实际类型来调用重写方法的。
我们也可以从例子中看出,变量的实际类型是在运行期确定的,重写方法的调用也是根据实际类型来调用的。
我们把这种在运行期根据实际类型来确定方法执行版本的分派动作,称为动态分派。
单分派和多分派
单分派:根据一个宗量对目标方法进行选择。动态分派属于单分派
多分派:根据多余一个宗量对目标方法进行选择。静态分派属于多分派