前言
上一篇講解了靜態分派和動態分派,還不清楚的同學可以先看看:
java虛擬機之方法調用(上)——靜態分派與動態分派
單分派與多分派
靜態分派和動態分派是根據判斷方法的執行版本的時期來分的,而單分派與多分派是根據方法的接收者和方法的參數的種數來分的。
我們把方法的接收者和方法的參數統稱爲宗量。
具體來說:
單分派是根據一個宗量對目標方法進行選擇。
多分派是根據多於一個宗量對目標進行選擇。
感覺還是有點抽象,其實簡單的說:
單分派是指調用一個對象的方法僅由對象的類型決定。
多分派是指調用一個對象的方法不僅由對象的類型決定,同時還由其他因素決定,比如方法參數的類型等等。
那到底靜態分派和動態分派與單分派和多分派有什麼關係呢?
下面我們就來一一分析,先看一段代碼:
public class Main {
static class Tencent {
}
static class Alibaba {
}
static class Father {
public void findJob(Tencent tencent) {
System.out.println("father work in tencent");
}
public void findJob(Alibaba ali) {
System.out.println("father work in alibaba");
}
}
static class Son extends Father {
@Override
public void findJob(Tencent tencent) {
System.out.println("son work in tencent");
}
@Override
public void findJob(Alibaba ali) {
System.out.println("son work in alibaba");
}
}
public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.findJob(new Tencent());
son.findJob(new Alibaba());
}
}
這個一個栗子裏面,沒有了上一篇博客裏面那麼單一,只有一個動態分派或者一個靜態分派,這個栗子兩者都包括,開發中遇到這樣的也更多一點。
對於這一個栗子,我們先從編譯期間看起:
先看看編譯的結果:
同樣的,我們找重點看,這裏先看24行,編譯的結果是Father.findJob(Tencent)
,說明編譯時這裏確定了兩個地方:一個是接收者類型Father,還有一個是Father裏面重載的版本findJob(Tencent)
,對於確定重載的版本,其實就是有參數的不同類型摻和進來了。
從此可以看出,其實在編譯期間,根據兩個宗量來選擇了目標方法,所以我們說java的靜態分派是多分派類型。
也可以說在Father類中,同一個類中重載的方法調用,是有類的聲明類型和方法參數類型(宗數大於一)決定的。所以是多分派。
如果上來還是有點迷糊的話,不要緊,先看看下面運行期間的分派過程:
由於剛剛我們看到了編譯的結果,在第35行:我們明確了在一個類裏面,調用的方法版本是findJob(Alibaba),所以在運行的時候,虛擬機不會再關心選擇哪一個方法的問題,因爲這個時候參數的靜態類型、實際類型都對方法的選擇不會產生影響了;它現在要關心的是方法的接收者的實際類型,因爲不同的實際類型可能有不同的重寫版本,所以現在唯一能影響方法的選擇的是方法接收者的實際類型這一個宗量,所以java語言的動態分派是單分派類型。
總結
由於java在編譯期間,同一個類中重載的方法調用,是有類的靜態類型和方法參數類型(宗量大於一)決定的。
而在運行時,重寫方法的調用,是由類的實際類型決定的(宗量爲一),這也就有了多態。
其實在JDK7以後,加入了invokedynamic
指令,而這個指令又使java對多態語言有了一定的支持。而JDK8又加入了lambda,感覺今後java對動態語言的支持會越來越多,所以對於分派的結論也可能會有一定的改變,因爲動態語言在編譯期間是不會檢測一個變量的類型的,因爲動態語言的變量根本沒有類型,然而只有變量的值有類型。類似的動態語言有javascript、php、python、ruby等。要是讀者有興趣的話,可以去研究研究。