1.理解前提
在理解静态分派和动态分派之前我们需要先理解静态类型和动态类型。
静态类型,是在编译期间可知的,什么意思呢,就是在使用javac命令编译java文件时确定的指令集。
动态类型,则是编译期间不可知,只有在运行期间才能确定的。
2.静态分派
为了更好的说明静态分派,参考如下重载方法的代码
public class Person {
public void sayHello() {
System.out.println("hello person");
}
public void sayHello(String hello) {
System.out.println(hello);
}
public void sayGoodBye() {
System.out.println("goodbye person");
}
public static void main(String[] args) {
Person person = new Person();
person.sayHello();
person.sayHello("hello again Person");
}
}
然后使用javac 命令反编译Person.class文件得到如下
javac -c -v Person.class
Classfile /E:/Pro_bak/QGZWXX/QGZWXX/WebContent/js/sys/Person.class
Last modified 2019-6-20; size 909 bytes
MD5 checksum 71d7cd35bec0c71e958b464fb68b1722
Compiled from "Person.java"
public class array.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #11.#31 // java/lang/Object."<init>":()V
#2 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #34 // hello person
#4 = Methodref #35.#36 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #37 // goodbye person
#6 = Class #38 // array/Person
#7 = Methodref #6.#31 // array/Person."<init>":()V
#8 = Methodref #6.#39 // array/Person.sayHello:()V
#9 = String #40 // hello again Person
#10 = Methodref #6.#41 // array/Person.sayHello:(Ljava/lang/String;)V
#11 = Class #42 // java/lang/Object
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Larray/Person;
#19 = Utf8 sayHello
#20 = Utf8 (Ljava/lang/String;)V
#21 = Utf8 hello
#22 = Utf8 Ljava/lang/String;
#23 = Utf8 sayGoodBye
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 person
#29 = Utf8 SourceFile
#30 = Utf8 Person.java
#31 = NameAndType #12:#13 // "<init>":()V
#32 = Class #43 // java/lang/System
#33 = NameAndType #44:#45 // out:Ljava/io/PrintStream;
#34 = Utf8 hello person
#35 = Class #46 // java/io/PrintStream
#36 = NameAndType #47:#20 // println:(Ljava/lang/String;)V
#37 = Utf8 goodbye person
#38 = Utf8 array/Person
#39 = NameAndType #19:#13 // sayHello:()V
#40 = Utf8 hello again Person
#41 = NameAndType #19:#20 // sayHello:(Ljava/lang/String;)V
#42 = Utf8 java/lang/Object
#43 = Utf8 java/lang/System
#44 = Utf8 out
#45 = Utf8 Ljava/io/PrintStream;
#46 = Utf8 java/io/PrintStream
#47 = Utf8 println
{
public array.Person();
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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Larray/Person;
public void sayHello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello person
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Larray/Person;
public void sayHello(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_1
4: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 10: 0
line 11: 7
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Larray/Person;
0 8 1 hello Ljava/lang/String;
public void sayGoodBye();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String goodbye person
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 14: 0
line 15: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Larray/Person;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #6 // class array/Person
3: dup
4: invokespecial #7 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #8 // Method sayHello:()V
12: aload_1
13: ldc #9 // String hello again Person
15: invokevirtual #10 // Method sayHello:(Ljava/lang/String;)V
18: return
LineNumberTable:
line 18: 0
line 19: 8
line 20: 12
line 21: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
8 11 1 person Larray/Person;
}
SourceFile: "Person.java"
我们重点关注反编译文件中的图示内容
在java代码里面分别调用了sayHello()和sayHello(String hello)两个重载的方法。
通过图中红框我们可以看出,在编译文件中已经分别确定了调用的方法,没有混淆,而这个过程并没有经过虚拟机运行,所以这个确认重载方法调用的过程在编译期间就完成了,这个过程就是静态分派。而两个方法中的参数就可以成为是静态类型。总而言之,根据静态类型定位方法执行版本的分派动作成为静态分派。
3.动态分派
为了更好的说明,我们使用重写,代码如下,两个类,Person和Man
public class Person {
public void sayHello() {
System.out.println("hello person");
}
public void sayHello(String hello) {
System.out.println(hello);
}
public void sayGoodBye() {
System.out.println("goodbye person");
}
public static void main(String[] args) {
Person person = new Person();
person.sayHello();
person.sayHello("hello again Person");
}
}
public class Man extends Person {
@Override
public void sayGoodBye() {
System.out.println("goodbye man");
}
public static void main(String[] args) {
Person person = new Person();
person.sayGoodBye();
person = new Man();
person.sayGoodBye();
}
}
如代码所示重写了sayGoodBye(),这个时候我们反编译Man.class
javac -c -v Man.class
结果如下
Classfile /E:/Pro_bak/QGZWXX/QGZWXX/WebContent/js/sys/Man.class
Last modified 2019-6-20; size 681 bytes
MD5 checksum dc174ca15248b325b11b4e27edbdff77
Compiled from "Man.java"
public class array.Man extends array.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#25 // array/Person."<init>":()V
#2 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #28 // goodbye man
#4 = Methodref #29.#30 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #31 // array/Person
#6 = Methodref #5.#32 // array/Person.sayGoodBye:()V
#7 = Class #33 // array/Man
#8 = Methodref #7.#25 // array/Man."<init>":()V
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Larray/Man;
#16 = Utf8 sayGoodBye
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 person
#22 = Utf8 Larray/Person;
#23 = Utf8 SourceFile
#24 = Utf8 Man.java
#25 = NameAndType #9:#10 // "<init>":()V
#26 = Class #34 // java/lang/System
#27 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#28 = Utf8 goodbye man
#29 = Class #37 // java/io/PrintStream
#30 = NameAndType #38:#39 // println:(Ljava/lang/String;)V
#31 = Utf8 array/Person
#32 = NameAndType #16:#10 // sayGoodBye:()V
#33 = Utf8 array/Man
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (Ljava/lang/String;)V
{
public array.Man();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method array/Person."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Larray/Man;
public void sayGoodBye();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String goodbye man
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Larray/Man;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #5 // class array/Person
3: dup
4: invokespecial #1 // Method array/Person."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #6 // Method array/Person.sayGoodBye:()V
12: new #7 // class array/Man
15: dup
16: invokespecial #8 // Method "<init>":()V
19: astore_1
20: aload_1
21: invokevirtual #6 // Method array/Person.sayGoodBye:()V
24: return
LineNumberTable:
line 10: 0
line 11: 8
line 12: 12
line 13: 20
line 14: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
8 17 1 person Larray/Person;
}
SourceFile: "Man.java"
重点关注图示部分
图中红框标记显示,两个调用都是Person中的sayGoodBye()方法,但是我们都知道,第二个应该是调用Man中重写的sayGoodBye(),所以编译期间具体调用的对象是不能确定的,要到虚拟机中运行的时候才能确定,总而言之,在虚拟机运行期间确定分派的过程称为动态分派。