在上兩篇中分別對方法重載【https://www.cnblogs.com/webor2006/p/9723289.html】和方法重寫【https://www.cnblogs.com/webor2006/p/9797506.html】在字節碼中的表現進行了詳細的分析,其中得出如下結論:方法重載是靜態的,是編譯期行為;方法重寫是動態的,是運行期行為。
這次繼續來舉一個綜合的例子,既有方法重載又有方法重寫,進一步來闡述其靜態分派與動態分派的機制,如下:
那其結果是啥呢?咱們直接運行一下:
分析一下:
咱們來看一下該代碼對應的字節碼信息:
其實針對於方法調用動態分派的過程,虛擬機會在類的方法區建立一個虛方法表的數據結構(virtual method table,也簡稱vtable)。針對於invokeinterface指令來說,虛擬機會建議一個叫做接口方法的數據結構(interface method table,也簡單itable),其查找機制基本類似,下面用一個示例圖來對其進行理解:
其中虛方法表vtable中每一項都存放的是特定方法實際真正的入口調用地址,其中有一種情況,就是子類Dog只繼承了Animal但沒有重寫過Animal父類的方法,如下:
如果Dog子類重寫了父類的方法,那么當然方法就會存在於Dog的虛方法表中啦。所以說對於Object類來說里面定義了很多的方法,但是實際我們編寫的類可能很多都沒有重寫它里面的方法,那么其虛方法表中都是存在Object當中而非拷貝一份到我們具體子類當中。
另外虛方法表vtable還有一點就是:只要是子類和父類的方法描述是一樣的,那么它們在父類和子類的索引是一樣的,這樣當查找子類的方法時,由於索引跟父類是一模一樣的,則直接拿着子類該方法的索引到父類的方法表中的對應的索引就直接可以定位到了,比較高效。一般虛方法表都是在類的連接階段【類加載有加載、連接、初始化階段】進行的初始化。
下面來看一下這個程序,比較容易犯錯誤,看一下偽代碼:
如果說這樣調用:
肯定是木有問題的,很顯然就是Child的一個靜態調用,那如果這樣調用呢?
這個結果是編譯都通不過,不信的話咱們以之前的例子稍加修改一下:
為啥呢?其實這個從字節碼上就能夠解釋,對於這個程序:
其實會對應於字節碼的invokevirtural指令,是靜態行為,其參數為Parent.test3(),就類似於:
而Parent類很明顯沒有定義test3()這個方法嘛,當然就編譯不過了。