分派
靜態(tài)分派和動態(tài)分派
靜態(tài)分派案例:
public class fenpai {static abstract class A {}static class B extends A {}static class C extends A {} public static void sayHi(A a) {System.out.println("hi ,father");}public void sayHi(B b) {System.out.println("hi ,sister");}public void sayHi(C c) {System.out.println("hi ,brother");}public static void main(String[] args) {fenpai f = new fenpai();A a1 = new B();A a2 = new C();f.sayHi(a1);f.sayHi(a2);} } hi ,father hi ,father?
動態(tài)分派案例:
public class fenpai {static abstract class A {public void sayHi() {System.out.println("hi ,father");}}static class B extends A {@Overridepublic void sayHi() {System.out.println("hi ,father");}}static class C extends A {@Overridepublic void sayHi() {System.out.println("hi ,sister");}} public void sayHi(C c) {System.out.println("hi ,brother");}public static void main(String[] args) {fenpai f = new fenpai();A a1 = new B();A a2 = new C();a1.sayHi();a2.sayHi();} } hi ,father hi ,sister解釋一下兩種結果不同的原因:
父類A稱為變量的靜態(tài)類型,也叫外觀類型,子類BC稱為實際類型,靜態(tài)類型是編譯期可知的,而實際類型是運行期才知道。
結果不同的原因是:
- 靜態(tài)分配的虛擬機重載時,是通過參數(shù)的靜態(tài)類型而不是實際類型進行判定的,編譯器在編譯階段,直接根據(jù)靜態(tài)類型決定使用哪個版本。
- 而動態(tài)分配是方法的重寫,Java虛擬機是通過實際類型來判斷執(zhí)行的版本。
讓我們來看一下他們的二進制字節(jié)碼來了解編譯器干了什么?
?
靜態(tài)分配
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=4, args_size=10: new #1 // class fenpai3: dup4: invokespecial #44 // Method "<init>":()V7: astore_18: new #45 // class fenpai$B11: dup12: invokespecial #47 // Method fenpai$B."<init>":()V15: astore_216: new #48 // class fenpai$C19: dup20: invokespecial #50 // Method fenpai$C."<init>":()V23: astore_324: aload_225: invokestatic #51 // Method sayHi:(Lfenpai$A;)V28: aload_329: invokestatic #51 // Method sayHi:(Lfenpai$A;)V32: returnLineNumberTable:line 24: 0line 25: 8line 26: 16line 27: 24line 28: 28line 29: 32LocalVariableTa動態(tài)分配
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=4, args_size=10: new #1 // class fenpai3: dup4: invokespecial #34 // Method "<init>":()V7: astore_18: new #35 // class fenpai$B11: dup12: invokespecial #37 // Method fenpai$B."<init>":()V15: astore_216: new #38 // class fenpai$C19: dup20: invokespecial #40 // Method fenpai$C."<init>":()V23: astore_324: aload_225: invokevirtual #41 // Method fenpai$A.sayHi:()V28: aload_329: invokevirtual #41 // Method fenpai$A.sayHi:()V32: returnLineNumberTable:line 28: 0line 29: 8line 30: 16line 31: 24line 32: 28line 33: 32很清楚地看到靜態(tài)分配的25行 29行invokestatic
動態(tài)分配的25行,29行invokevirtual
這就是區(qū)別所在。
a)靜態(tài)分配在編譯器就決定了使用哪個重載版本,所以他把這個決定的重載方法寫入了invokestatic指令中。
b)動態(tài)分配,編譯器不知道實際類型,所以他不知道執(zhí)行哪個重寫版本,于是它只能寫入invokevirtual指令中。
invokevirtual指令執(zhí)行過程:
1)確定接受者的實際類型。
2)在操作數(shù)棧找到對應這個實際類型。
3)在類型中找到與常量中描述符合和簡單名稱都相同的方法,進行訪問權限檢驗,如果通過,則返回這個方法的直接引用,如果不通過,返回java.lang.IllegalAccessError 異常。
4)否則,按照繼承關系從上往下對這個實際類型的各個父類進行第三步的搜索和驗證功能。
5)如果沒有合適的方法,則拋出java.lang.AbstractMethodError。
總結
- 上一篇: 运行时栈帧结构
- 下一篇: Java的io类的使用场景