千萬別告訴別人,這是我從高工那偷聽來的Java方法分派策略

  • 2019 年 12 月 19 日
  • 筆記

閱讀文本大概需要 4 分鐘。

1

基礎

我們知道Java有一大特性就是多態。講到多態不得不想再深層次的研究下多態中的方法分派到底是採用什麼樣的策略的。

還是舉個例子來分析一下:

public class SuperClass {      public String getName() {          return "super";      }  }    public class SubClass extends SuperClass{      @Override      public String getName() {          return "sub";      }  }    public class MainClass{        public static void main(String... args) {          SuperClass superClass = new SubClass();          test(superClass);      }        public static void test(SuperClass superClass) {          System.out.println("a " + superClass.getName());      }        public static void test(SubClass subClass) {          System.out.println("b " + subClass.getName());      }  }

上面的程式碼中最終輸出的應該是多少呢? 別著急,咱們先分析下上面的輸出會有什麼不同,我們聲明的是一個父類對象,但是實例化的時候是以子類來實例化的,所以影響輸出結果的第一個問題是:

① getName()方法最後輸出的是"super"還是"sub",也就是調用的子類的getName還是父類的getName

第二個問題是:

② test()調用的是第一個以父類為參數的方法還是第二個以子類型為參數的方法

其實對於①問題,稍微有過編程經驗的人都清楚,getName肯定是調用的是子類的方法,因為本來就是子類的實例,這也是Java多態的一種體現。

作為一個初級工程師,以上都是應該知道的。

對於第二個問涉及的是Java編譯器在將test()編譯成位元組碼的時候,需要知道參數的類型,而參數類型就是你聲明變數的類型,也就是說會調用test(SuperClass)方法。所以最後會輸出"a sub"。

如果你已經知道這些了,恭喜你中級工程師的水平有了。

稍微總結一下上面的內容,Java的方法分派分為兩種:

  1. 靜態分派 – 方法重載分派
    1. 編譯器就確定
    2. 依據調用者的聲明類型和方法的參數類型匹配
  2. 動態分派 – 方法重寫分派
    1. 運行時確定
    2. 依據調用者的實際類型分派

2

發散一下

如果你做過Android開發,你一定對Groovy或多或少有一定的了解(我們在寫Gradle腳本時就是使用的Groovy語言)。我們也是知道在Groovy中也是可以寫Java的程式碼的,如果將上面的程式碼放到Groovy中,輸出的結果一樣嗎?

還真不一樣,輸出的是"b sub",這是Groovy不同之處哈,他在被編譯成位元組碼的時候,test()方法被編譯成了CallSite.callStatic方法,而這個方法會在運行時通過反射拿到參數的實際類型,然後決定調用哪個test方法,那實際類型當然是子類了,所以會調用以子類為參數類型的方法了。

到這裡,如果你能夠觸類旁通,不僅僅局限於一種語言,能夠多語言橫向對比,那麼顯然你具有了高工的氣質了。如果你有興趣的話,不妨對比下C++、Kotlin、Scala等語言之間有什麼區別。

本節內容實際上比較簡單直白,但是涉及到的點你平時不一定有認真思考過,只有比較系統的掌握了語言才能對他的編譯和運行機制了如指掌。所以只有多思考才能進步。