本文以Java多态的一些基本特征来谈一下分派调用。
在开始,依旧用常用方式,例子来引入,看一看下面例子的输出:
/**
*
* @author Sel
*
* 2014.4.3
*/
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)。
例子如下:
/**
*
* @author Sel
*
* 2014.4.3
*/
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变量的实际类型导致调用函数版本不同,我们就可以知道,虚拟机是根据变量的实际类型来调用重写方法的。
我们也可以从例子中看出,变量的实际类型是在运行期确定的,重写方法的调用也是根据实际类型来调用的。
我们把这种在运行期根据实际类型来确定方法执行版本的分派动作,称为动态分派。