java是一门面向对象的程序语言,因此java具备面向对象的三个特征:继承、封装和多态,而分派调用过程将会揭示多态性特征。
概念
在将不同分派之前,先看一个概念:
// Huamn Man Woman的关系如下
static interface Human {
}
static class Man implements Human {
}
static class Woman implements Human {
}
//声明变量
Human man = new Man();
我们将上面变量声明中的Human成为变量的静态类型(Static Type),或者外观类型,而后面的Man则成为变量的**实际类型(Actual Type).
变量的静态类型是在编译期可知的,而实际类型变化的结果在运行期才可确定,编译器在编译程序时并不知道一个对象的实际类型是什么,例如:
//实际类型变化
Human man = new Man();
man = new Woman();
//强制转换
Huamn human = new Man();
(Man)human //使用时,实际类型为Human
静态分派
例子
public class DispatcherTest {
static interface Human {
}
static class Man implements Human {
}
static class Woman implements Human {
}
public void sayHi(Human human) {
System.out.println("human say hi. ");
}
public void sayHi(Man man) {
System.out.println("man say hi. ");
}
public void sayHi(Woman woman) {
System.out.println("woman say hi. ");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
DispatcherTest test = new DispatcherTest();
test.sayHi(man);
test.sayHi(woman);
}
}
测试结果
human say hi.
human say hi.
分析
上面的例子是一个重载的例子,DispatcherTest包含三个say方法,方法参数的类型不同,但却存在继承关系。测试过程中使用了两个静态类型相同而实际类型不同的变量,但虚拟机(准确来说是编译器) 在重载时是通过参数的静态类型而不是实际类型作为判断依据的。且静态类型是编译期可知的。
所有依赖静态类型来定位方法执行的分派动作成为静态分派,典型应用是方法重载。静态分派发生在编译阶段,因此确定静态分派的动作实际不是由虚拟机来执行的
总结
"重载"通过参数的静态类型做判断。
备注
重载方法匹配优先级,例如
public class A {
public method(int a) {}
public method(long a) {}
public method(byte a) {}
public menthod(Object a) {}
}
调用A类的方法method的参数为char,char a = 'a'; A.mehtod(a)
,该如何选择...
实际上参数会自动转换,按照`char->int->long->float->double
的顺序转型进行匹配,但不会匹配到byte和shrot类型的重载。
动态分配
动态分配和多态性的另外一个重要特性:重写紧密相关。
例子
public class DispatcherTest {
static interface Human {
public void say();
}
static class Man implements Human {
@Override
public void say() {
System.out.println("Class Man say hi. ");
}
}
static class Woman implements Human {
@Override
public void say() {
System.out.println("Class Woman say hi. ");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.say();
woman.say();
}
}
测试结果
Class Man say hi.
Class Woman say hi.
分析
静态类型相同的两个变量执行相同的方法却表现了不同的行为,因为这两个变量的实际类型不同。
实际过程:
- 找到操作数栈顶的第一个元素所指向的对象的实际类型A;
- 如果C中找到与常量中的描述符合简单名称都相符的方法,权限验证,通过则直接返回这个方法的直接引用,查找结束。如果权限不通过,则返回java.lang.IllegalAccessError
- 否则,按继承关系从下往上依次对A的各个父类进行第2步的搜索和验证
- 始终没找到,抛出java.lang.MethodNotFoundError。
单分派和多分派
方法的接收者与方法的参数统称为方法的宗量,根据分派基于多少种宗量,可以将分派划分为单分派和多分派。单分派指依据一个宗量对目标方法进行选择,多分派则依据多于一个宗量对目标方法进行选择。
例子
public class DispatcherTest {
static class Animal {}
static class Cat extends Animal {}
static class Dog extends Animal {}
static class Man {
public void say(Animal animal) {
System.out.println("Class Man say animal. ");
}
public void say(Cat cat) {
System.out.println("Class Man say cat. ");
}
}
static class Woman extends Man {
@Override
public void say(Animal animal) {
System.out.println("Class Woman say hi. ");
}
@Override
public void say(Cat cat) {
System.out.println("Class Woman say cat. ");
}
}
public static void main(String[] args) {
Animal animal = new Animal();
Cat cat = new Cat();
Man man = new Man();
Woman woman = new Woman();
man.say(animal);
man.say(cat);
woman.say(animal);
woman.say(cat);
}
}
测试结果
Class Man say animal.
Class Man say cat.
Class Woman say hi.
Class Woman say cat.
分析
首先看静态分派过程(编译阶段编译器的选择),这时选择目标方法的依据有两个:一是静态类型是Man还是Women,二是方法参数 编译期已经决定目标方法的签名,虚拟机此时不会关心传递过来的参数具体是哪个,因为这时参数的静态类型、实际类型都对方法的选择不会构成任何影响,唯一可以影响虚拟机选择的因素只有此方法的接受者的实际类型是Man还是Woman。仅有一个宗量
总结
java是一门静态多分派,动态单分派语言,仍不是很理解。
Comments