重写的实现和动态分派(**Dispatch**)有紧密的联系,**分派**的大概意思是,指定调用多个实现中的某个实现。 ### 动态分派 比如说 ``` java 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(); } } ``` 当然,我们完全知道,会输出 ``` son Daughter ``` 但是,在表现类型为Father的情况下,他是怎么知道son对应的print是输出`son`的那个呢? javap 输出字节码: ``` 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."":()V #2 = Class #23 // DynamicDispatch$Son #3 = Methodref #2.#22 // DynamicDispatch$Son."":()V #4 = Class #24 // DynamicDispatch$Daughter #5 = Methodref #4.#22 // DynamicDispatch$Daughter."":()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 #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 // "":()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."":()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."":()V 7: astore_1 8: new #4 // class DynamicDispatch$Daughter 11: dup 12: invokespecial #5 // Method DynamicDispatch$Daughter."":()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是如何查找对应的方法的呢? 1. 找到操作数栈栈顶元素,拿到该元素所指的对象类型C 2. 在C中寻找符合要求的方法,如果找到了,就进行访问权限校验,通过了就进行返回,不通过抛`IllegalAccessError` 3. 如果没找到,根据继承关系,向父类进行查找 4. 最后还是没找到就抛`AbstractMethodError` 那为什么字节码是先`aload_1`的呢?因为字节码是编译后就确定的了,所以只能是编译器这边做的工作,大概是*类型推断*之类的(不确定) 运行时进行确定实际类型、确定执行的方法版本的过程叫动态分派。 这就是jvm对动态分派的处理,也就是实现了java的重写,程序员借助重写编写多态程序。 ### 静态分派 静态分派就没这么复杂, 对应的是方法重载,(有人说方法重载不算多态的表现形式,因为编译完就确定了要使用哪一个方法) ``` java 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)` 上边的代码的字节码是这样的: ``` ByteCode { 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."":()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两个宗量(参数不同) ``` java public class Book{ public void read(){} public static void doRead(Book b){ b.read(); } public static void doRead(Picture p){ p.read(); } } ``` 所以说java是静态多分派的语言,而在动态分派的时候总是在找栈顶引用对象里的那一个方法(也就是说参数不变的),所以动态分派的时候是 单分派的。**Java是一门静态多分派,动态单分派的语言**