從Java繼承類的重名static函數淺談解析調用與分派


今天被實習生問了這么個問題:
在java中,static成員函數是否可以被重寫呢?

結論是,你可以在子類中重寫一個static函數,但是這個函數並不能像正常的非static函數那樣運行。

也就是說,雖然你可以定義一個重寫函數,但是該函數沒有多態特性。讓我們測試一下:

 1 class  testClass1{
 2     static void SMethod(){
 3         System.out.println("static in testClass1");
 4     }
 5 }
 6 class testClass2 extends testClass1{
 7     static void SMethod(){
 8         System.out.println("static in testClass2");
 9     }
10 }
11 public class MainClass{
12     public static void main(String... args){
13         testClass1 tc1=new testClass2();
14         testClass2 tc2 =new testClass2();
15         tc1.SMethod(); //輸出結果為 static in testClass1
16         tc2.SMethod(); //輸出結果為 static in testClass2
17     }
18 }

從結果中可以看到,當我們用父類的實例引用(實際上該實例是一個子類)調用static函數時,調用的是父類的static函數。

原因在於方法被加載的順序。

當一個方法被調用時,JVM首先檢查其是不是類方法。如果是,則直接從調用該方法引用變量所屬類中找到該方法並執行,而不再確定它是否被重寫(覆蓋)。如果不是,才會去進行其它操作(例如動態方法查詢)

可能有的人一拍大腿,這不就是java的靜態/動態分派么!

有點像,但還真不是,靜態分派與動態分派是用來確定重載和重寫邏輯的。在重載過程中,編譯器根據方法參數的靜態類型(比如tc1的靜態類型是class1,tc2的是class2,但本文這里不是重載!)來確定使用方法的版本,這叫做靜態分派。動態分派是用於方法重寫的,比如我調用一個類A的方法f,如果該類有子類a,那么我以a來調用f的時候,調用的實際是a.f而非A.f。

看起來還真的像動態分派是不是?但是結果不符合啊!

這里的原因在於,動態分派時,我們實際是在討論Java的invokevirtual指令的行為:這個指令首先會去尋找調用者的運行時類型,然后在其方法表里面尋找匹配的方法,如果找不到,再從其父類里找。這個過程就是Java中方法重寫的本質,也就是動態分派。

而static方法是通過invokestatic指令來調用的。由於static方法是一種編譯期可知,運行期不可變的方法,所以盡管子類和父類都有同樣的方法名,而事實上它們是不同的方法,也是完全可以區分的方法。在調用static方法時,編譯器就會直接在類加載時把其符號引用解析為直接引用,不存在說子類找不到方法之后再去父類找這種行為,所以也叫解析調用。

這就是上面的例子中看起來像是重寫的方法卻沒有產生重寫的效果的原因。

全文完。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM