前一篇文章聊了一下方法調用中的靜態(tài)分派颠猴,這次我們聊下動態(tài)分派的過程砸喻,他和多態(tài)的另外一個重要體現(xiàn)-重寫(override)有著很密切的關聯(lián)若未。先看下如下代碼:
運行結果:
man say hello
woman say hello
woman say hello
這個運行結果對于習慣了面向對象思維的java程序員來說相對比較簡單辐真。但是虛擬機是如何知道要調用哪個方法呢糕珊?
顯然不能根據(jù)靜態(tài)類型來決定磷斧,因為金泰類型同樣都是human的兩個變量man和woman在調用sayHello()方法時執(zhí)行了不同的行為振愿,并且變量man在兩次調用中執(zhí)行了不同的方法。導致這個現(xiàn)象的原因很明顯弛饭,是這兩個變量的實際類型不同冕末,Java虛擬機是如何根據(jù)實際類型來分派方法執(zhí)行版本呢,我們通過javap命令輸出這段代碼的字節(jié)碼侣颂,參見下圖:
0-15行的字節(jié)碼是準備動作栓霜,作用是建立man和woman的內存空間、調用Man和Woman類的實例構造器横蜒,將這兩個實例的引用存放在第1胳蛮、2個局部變量表Slot之中,這個動作也對應代碼中的這兩句:
Human man = new Man();
Human woman = new Woman();
接下來16-21句是關鍵部分丛晌,16仅炊、20分別把剛剛建立的兩個對象的引用壓倒棧頂,這兩個對象時將要執(zhí)行sayHello()方法的所有者澎蛛,稱為接收者抚垄;17和21句是方法的調用指令,這兩條調用指令從字節(jié)碼角度來看谋逻,無論指令還是參數(shù)完全一樣呆馁,但是這兩句指令最終執(zhí)行的目標方法并不相同。原因就需要從invokevirtual指令的多態(tài)查找過程開始說起毁兆,invokevirtual指令的運行時解析過程大致分為以下幾個步驟:
1浙滤、找到操作數(shù)棧頂?shù)牡谝粋€元素所指向的對象實際類型,記做C气堕。
2纺腊、如果在類型C中找到與常量中的描述符合簡單名稱都相符的方法,則進行訪問權限校驗茎芭,如果通過則返回這個方法的直接引用揖膜,查找過程結束;如果不通過梅桩,則返回java.lang.IllegalAccessError異常壹粟。
3、否則宿百,按照繼承關系從下往上依次對C的各個父類進行第2步的搜索和驗證操作趁仙。
4洪添、如果始終沒有找到合適的方法,則拋出java.lang.AbstractMethodError異常
由于invokevirtual指令執(zhí)行的第一步就是在運行期確定接收者的實際類型幸撕,所以兩次調用中invokevirtual指令吧常量池中的類方法符號引用解析到了不同的直接引用上,這個過程就是java語言中方法重寫的本質外臂。我們把這種在運行期根據(jù)實際類型確定方法執(zhí)行版本的分派過程稱為動態(tài)分派坐儿。