Java多态和Jvm的分派及其他

重写的实现和动态分派（Dispatch）有紧密的联系，分派的大概意思是，指定调用多个实现中的某个实现。

动态分派

比如说

Copy
public class DynamicDispatch {

static abstract class Father {
public abstract void print();
}

static class Son extends Father {
public void print() {
System.out.println("son");
}
}

static class Daughter extends Father {
public void print() {
System.out.println("Daughter");
}
}

public static void main(String[] args) {
Father son = new Son();
Father dou = new Daughter();

son.print();
dou.print();
}
}

当然，我们完全知道，会输出

Copy
son
Daughter

但是，在表现类型为 Father 的情况下，他是怎么知道 son 对应的 print 是输出son的那个呢？

javap 输出字节码：

Copy
Classfile /D:/tech/java/vmdemo/src/main/java/DynamicDispatch.class
Last modified 2019-1-10; size 499 bytes
MD5 checksum a5660428b8d1d4ed7fb4f04bd86f7738
Compiled from "DynamicDispatch.java"
public class DynamicDispatch
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // DynamicDispatch$Son
#3 = Methodref #2.#22 // DynamicDispatch$Son."<init>":()V
#4 = Class #24 // DynamicDispatch$Daughter
#5 = Methodref #4.#22 // DynamicDispatch$Daughter."<init>":()V
#6 = Methodref #12.#25 // DynamicDispatch$Father.print:()V
#7 = Class #26 // DynamicDispatch
#8 = Class #27 // java/lang/Object
#9 = Utf8 Daughter
#10 = Utf8 InnerClasses
#11 = Utf8 Son
#12 = Class #28 // DynamicDispatch$Father
#13 = Utf8 Father
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 SourceFile
#21 = Utf8 DynamicDispatch.java
#22 = NameAndType #14:#15 // "<init>":()V
#23 = Utf8 DynamicDispatch$Son
#24 = Utf8 DynamicDispatch$Daughter
#25 = NameAndType #29:#15 // print:()V
#26 = Utf8 DynamicDispatch
#27 = Utf8 java/lang/Object
#28 = Utf8 DynamicDispatch$Father
#29 = Utf8 print
{
public DynamicDispatch();
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 5: 0

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class DynamicDispatch$Son
3: dup
4: invokespecial #3 // Method DynamicDispatch$Son."<init>":()V
7: astore_1
8: new #4 // class DynamicDispatch$Daughter
11: dup
12: invokespecial #5 // Method DynamicDispatch$Daughter."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method DynamicDispatch$Father.print:()V
20: aload_2
21: invokevirtual #6 // Method DynamicDispatch$Father.print:()V
24: return
LineNumberTable:
line 24: 0
line 25: 8
line 27: 16
line 28: 20
line 29: 24
}
SourceFile: "DynamicDispatch.java"
InnerClasses:
static #9= #4 of #7; //Daughter=class DynamicDispatch$Daughter of class DynamicDispatch
static #11= #2 of #7; //Son=class DynamicDispatch$Son of class DynamicDispatch
static abstract #13= #12 of #7; //Father=class DynamicDispatch$Father of class DynamicDispatch

0-15 实在 new 那两个对象，和初始化，然后把应用存在 local 变量里边：[son,doughter]

然后，先拿出变量位置为1的对象引用 (son)，放到栈顶，调用invokevirtual,#6是个方法，invokevirtual 指令自动找到当前对象里

叫DynamicDispatch$Father.print:()V 的方法，然后调用，也就是调用了son.print();

invokevirtual

整个过程的关键是invokevirtual,invokevirtual 是如何查找对应的方法的呢？

找到操作数栈栈顶元素，拿到该元素所指的对象类型 C

在 C 中寻找符合要求的方法，如果找到了，就进行访问权限校验，通过了就进行返回，不通过抛IllegalAccessError

如果没找到，根据继承关系，向父类进行查找

最后还是没找到就抛AbstractMethodError

那为什么字节码是先aload_1的呢？因为字节码是编译后就确定的了，所以只能是编译器这边做的工作，大概是类型推断之类的（不确定）

运行时进行确定实际类型、确定执行的方法版本的过程叫动态分派。

这就是 jvm 对动态分派的处理，也就是实现了 java 的重写，程序员借助重写编写多态程序。

静态分派

静态分派就没这么复杂，对应的是方法重载，（有人说方法重载不算多态的表现形式，因为编译完就确定了要使用哪一个方法）

Copy
public class StaticDispatch {

static void print(int i){
System.out.println("int!");
}

static void print(char i){
System.out.println("char!");
}

static void print(boolean i){
System.out.println("bool!");
}

public static void main(String[] args) {
print(1);
print('1');
print(true);
}
}

方法重载调用的是尽可能相近的方法，比如假如没有print(char)而调用print('a')的时候，会给你调用print(int)

上边的代码的字节码是这样的：

Copy
{
public StaticDispatch();
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 5: 0

static void print(int);
descriptor: (I)V
flags: ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String int!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8

static void print(char);
descriptor: (C)V
flags: ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String char!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 12: 0
line 13: 8

static void print(boolean);
descriptor: (Z)V
flags: ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String bool!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 16: 0
line 17: 8

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: iconst_1
1: invokestatic #7 // Method print:(I)V
4: bipush 49
6: invokestatic #8 // Method print:(C)V
9: iconst_1
10: invokestatic #9 // Method print:(Z)V
13: return
LineNumberTable:
line 20: 0
line 22: 4
line 24: 9
line 25: 13
}

编译完了就给你排好要调用哪个方法了。

单分派、多分派

宗量， 个人理解是方法的接收者和方法的参数叫方法的宗量

根据宗量的大小可以区分是单分派还是多分派

比如一下代码，在分派的时候有 doRead 两个宗量 (参数不同)

Copy
public class Book{
public void read(){}

public static void doRead(Book b){
b.read();
}

public static void doRead(Picture p){
p.read();
}
}

所以说 java 是静态多分派的语言，而在动态分派的时候总是在找栈顶引用对象里的那一个方法（也就是说参数不变的），所以动态分派的时候是

单分派的。Java 是一门静态多分派，动态单分派的语言