banner
NEWS LETTER

单路分发和多路分发

Scroll down

单路分发

前置概念:
接收者对象:方法调用的目标对象
动态链接:在Java源文件被编译到字节码文件时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里。
引用类型/静态类型:java对象的声明类型
运行类型:jvm中对象的实际类型,是堆区数据的对象类型。

java对象的多态性导致其引用类型和运行类型两者的区分,且各自的方法可能出现重写的现象,因而在对象执行过程中需要找到正确的方法。jvm只有在运行阶段才会创建对象,调用方法时根据动态链接找到堆区的方法实现。。

java单路分发:接收者对象(实际上是指堆区创建的对象,也就是运行类型)调用方法时根据动态链接找到方法区的真正实现。

单路分发定义:查找方法只能根据接收者对象判断,而不考虑方法参数的实际类型。

例如:string.valueof(bi) 接收者对象为string,单路分发只会考虑方法参数的引用类型,而非实际类型。

注意:我们说java方法调用是单路分发,不考虑方法参数的实际类型。其中对于实际类型的理解需要和重载相区分,因为重载是从参数的引用类型进行选择。而我们所说的单路分发、多路分发实际上是根据方法的实际运行类型进行选择方法的具体实现(动态链接),而java的机制仅仅只支持根据接受者对象来选择方法的动态链接,因此java的分发机制是单路分发。

  • 方法重载的匹配:编译器在编译时根据参数类型选择最匹配的方法。
  • 编译器生成的字节码:编译器将方法调用转换为字节码指令,这些指令包含了方法调用的具体信息。
  • 动态链接:在类加载时,方法引用被解析为具体的方法地址。在方法调用时,JVM 根据接收者对象的实际类型选择具体的方法实现。

两路分发和多路分发

参考;Java 枚举 enum 详解 - 低吟不作语 - 博客园

java实现两路分发实际上就是在方法调用中再次触发方法调用,根据方法参数的实际类型进行选择方法。

硬编码实现

1
2
3
4
5
## 可以使用枚举、swich简化代码实现
@Override public Outcome compete(Item it)
{ return
it.eval(this);
}

且注意,只有it.eveal(this)找到的方法才是期望的方法,第一次执行找到compete方法是实现两路分发的过程。

EnumMap 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package enums;
import java.util.*;
import static enums.Outcome.*;
enum RoShamBo5 implements Competitor<RoShamBo5> {
PAPER, SCISSORS, ROCK;
static EnumMap<RoShamBo5,EnumMap<RoShamBo5,Outcome>>
table = new EnumMap<>(RoShamBo5.class);
static {
for(RoShamBo5 it : RoShamBo5.values())
table.put(it, new EnumMap<>(RoShamBo5.class));
initRow(PAPER, DRAW, LOSE, WIN);
initRow(SCISSORS, WIN, DRAW, LOSE);
initRow(ROCK, LOSE, WIN, DRAW);
}
static void initRow(RoShamBo5 it,
Outcome vPAPER, Outcome vSCISSORS, Outcome vROCK) {
EnumMap<RoShamBo5,Outcome> row =
RoShamBo5.table.get(it);
row.put(RoShamBo5.PAPER, vPAPER);
row.put(RoShamBo5.SCISSORS, vSCISSORS);
row.put(RoShamBo5.ROCK, vROCK);
}
@Override
public Outcome compete(RoShamBo5 it) {
return table.get(this).get(it);
}
public static void main(String[] args) {
RoShamBo.play(RoShamBo5.class, 20);
}
}

实际上依旧是二次选择,只是没有触发方法的动态链接机制,而是通过map类型手动填充了一张表格进行分发。

其他文章