Java 方法调用之动态单分派与静态单多分派

定义:根据一个宗量的类型进行方法的选择称为单分派;

根据多于一个宗量的类型对方法的选择称为多分派;

方法的接受者与方法的参数统称为方法的宗量

 

[java]
public class Dispatcher {
static class QQ {}
static class _360 {}

public static class Father {
public void hardChoice(QQ arg) {
System.out.println("father choose QQ");
}

public void hardChoice(_360 arg) {
System.out.println("father choose _360");
}
}

public static class Son extends Father {
@Override
public void hardChoice(QQ arg) {
System.out.println("son choose QQ");
}

@Override
public void hardChoice(_360 arg) {
System.out.println("son choose 360");
}
}

public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
}
[/java]

执行javap:
javap -verbose -c Dispatcher

执行结果如下所示:

[java]
Classfile /work/test/Dispatcher.class
Last modified Jan 17, 2018; size 581 bytes
MD5 checksum 09a577366cd2c3909d09c89558423dff
Compiled from "Dispatcher.java"
public class Dispatcher
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#27 // java/lang/Object."<init>":()V
#2 = Class #28 // Dispatcher$Father
#3 = Methodref #2.#27 // Dispatcher$Father."<init>":()V
#4 = Class #29 // Dispatcher$Son
#5 = Methodref #4.#27 // Dispatcher$Son."<init>":()V
#6 = Class #30 // Dispatcher$_360
#7 = Methodref #6.#27 // Dispatcher$_360."<init>":()V
#8 = Methodref #2.#31 // Dispatcher$Father.hardChoice:(LDispatcher$_360;)V
#9 = Class #32 // Dispatcher$QQ
#10 = Methodref #9.#27 // Dispatcher$QQ."<init>":()V
#11 = Methodref #2.#33 // Dispatcher$Father.hardChoice:(LDispatcher$QQ;)V
#12 = Class #34 // Dispatcher
#13 = Class #35 // java/lang/Object
#14 = Utf8 Son
#15 = Utf8 InnerClasses
#16 = Utf8 Father
#17 = Utf8 _360
#18 = Utf8 QQ
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 main
#24 = Utf8 ([Ljava/lang/String;)V
#25 = Utf8 SourceFile
#26 = Utf8 Dispatcher.java
#27 = NameAndType #19:#20 // "<init>":()V
#28 = Utf8 Dispatcher$Father
#29 = Utf8 Dispatcher$Son
#30 = Utf8 Dispatcher$_360
#31 = NameAndType #36:#37 // hardChoice:(LDispatcher$_360;)V
#32 = Utf8 Dispatcher$QQ
#33 = NameAndType #36:#38 // hardChoice:(LDispatcher$QQ;)V
#34 = Utf8 Dispatcher
#35 = Utf8 java/lang/Object
#36 = Utf8 hardChoice
#37 = Utf8 (LDispatcher$_360;)V
#38 = Utf8 (LDispatcher$QQ;)V
{
public Dispatcher();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class Dispatcher$Father
3: dup
4: invokespecial #3 // Method Dispatcher$Father."<init>":()V
7: astore_1
8: new #4 // class Dispatcher$Son
11: dup
12: invokespecial #5 // Method Dispatcher$Son."<init>":()V
15: astore_2
16: aload_1
17: new #6 // class Dispatcher$_360
20: dup
21: invokespecial #7 // Method Dispatcher$_360."<init>":()V
24: invokevirtual #8 // Method Dispatcher$Father.hardChoice:(LDispatcher$_360;)V
27: aload_2
28: new #9 // class Dispatcher$QQ
31: dup
32: invokespecial #10 // Method Dispatcher$QQ."<init>":()V
35: invokevirtual #11 // Method Dispatcher$Father.hardChoice:(LDispatcher$QQ;)V
38: return
LineNumberTable:
line 37: 0
line 38: 8
line 39: 16
line 40: 27
line 41: 38
}
SourceFile: "Dispatcher.java"
InnerClasses:
public static #14= #4 of #12; //Son=class Dispatcher$Son of class Dispatcher
public static #16= #2 of #12; //Father=class Dispatcher$Father of class Dispatcher
static #17= #6 of #12; //_360=class Dispatcher$_360 of class Dispatcher
static #18= #9 of #12; //QQ=class Dispatcher$QQ of class Dispatcher

[/java]

 

从上面的字节码指令中可以看到,两次方法调用
[java]
father.hardChoice(new _360());
son.hardChoice(new QQ());
[/java]
对应的字节码指令都是一样的,只是参数不同而已:
[java]
invokevirtual #8; //Method Dispatcher$Father.hardChoice:(LDispatcher$_360;)V
invokevirtual #11; //Method Dispatcher$Father.hardChoice:(LDispatcher$QQ;)V
[/java]
 

由此可见,在class文件中都是调用Father的hardChoice()方法。

解析

在java源代码进行编译的过程中,发生了这么个事情。
首先确定方法的接收者,发现两个对象变量的静态类型都是Father类型的,因此在class文件中写的Father类中方法的符号引用。
再者,对于方法参数,一个是_360对象,一个是QQ对象,按照静态类型匹配的原则,自然找到各自的方法。
上面的两步都是在编译器中做出的,属于静态分派,在选择目标方法时根据了两个宗量,是多分派的。因此,静态分派属于多分派类型。
当java执行时,当执行到son.hardChoice(new QQ()); 时,发现son的实际类型是Son,因此会调用Son类中的方法。在执行father.hardChoice(new _360()); 时也有这个过程,只不过father的实际类型就是Father而已。发现,在目标选择时只依据了一个宗量,是单分派的。因此,动态分派属于单分派类型。

结论

到目前为止,java语言是一个静态多分派,动态单分派的语言。下篇博文,将讨论讨论动态分派(即多态)的实现原理

Jason.wang

When you find your talent can't afford to be ambitious, be quiet and study !

You may also like...