(JVM)Java虛擬機器:靜態分派 & 動態分派 原理解析

前言
- 瞭解 行為方法分派 有利於在行為分派時時進行一些功能操作
- 本文全面講解行為分派的型別:靜態 & 動態行為分派,希望你們會喜歡。
在接下來的日子,我會推出一系列講解 JVM
的文章,具體如下;感興趣可持續關注 ofollow,noindex">Carson_Ho的安卓開發筆記

示意圖
目錄

示意圖
1. 知識儲備
1.1 分派
- 定義:確定執行哪個方法 的過程
- 分類:靜態分派 & 動態分派。下面我將詳細講解。
1.2 變數的靜態型別 & 動態型別
先看下面的程式碼
public class Test { static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } // 執行程式碼 public static void main(String[] args) { Human man = new Man(); // 變數man的靜態型別 = 引用型別 = Human:不會被改變、在編譯器可知 // 變數man的動態型別 = 例項物件型別 = Man:會變化、在執行期才可知 } }
即:
- 變數的靜態型別 = 引用型別 :不會被改變、在編譯器可知
- 變數的動態型別 = 例項物件型別 :會變化、在執行期才可知
下面,我將詳細講解 Java
中的分派型別:靜態分派 & 動態分派
2. 靜態分派
-
定義
根據 變數的靜態型別 進行方法分派 的 行為
Java
-
應用場景
方法過載(
OverLoad
) -
例項說明
public class Test { // 類定義 static abstract class Human { } // 繼承自抽象類Human static class Man extends Human { } static class Woman extends Human { } // 可供過載的方法 public void sayHello(Human guy) { System.out.println("hello,guy!"); } public void sayHello(Man guy) { System.out.println("hello gentleman!"); } public void sayHello(Woman guy) { System.out.println("hello lady!"); } // 測試程式碼 public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); Test test = new Test(); test.sayHello(man); test.sayHello(woman); } } // 執行結果 hello,guy! hello,guy!
根據上述的講解,大家應該明白執行結果的原因:
- 方法過載(
OverLoad
) = 靜態分派 = 根據 變數的靜態型別 確定執行(過載)哪個方法 - 所以上述的方法執行時,是根據變數(
man
、woman
)的靜態型別(Human
)確定過載sayHello()
中引數為Human guy
的方法,即sayHello(Human guy)
特別注意
a. 變數的靜態型別 發生變化 的情況
可通過 強制型別轉換 改變 變數的靜態型別
Human man = new Man(); test.sayHello((Man)man); // 強制型別轉換 // 此時man的靜態型別從 Human 變為 Man // 所以會呼叫sayHello()中引數為Man guy的方法,即sayHello(Man guy)
b. 靜態分派的優先順序匹配問題
- 問題描述:
- 背景
現需要進行靜態分派 - 問題
程式中 沒有顯示指定 靜態型別 - 解決方案
程式會根據 靜態型別的優先順序 從而選擇 優先的靜態型別進行方法分配。
- 例項說明
public class Overload { private static void sayHello(char arg){ System.out.println("hello char"); } private static void sayHello(Object arg){ System.out.println("hello Object"); } private static void sayHello(int arg){ System.out.println("hello int"); } private static void sayHello(long arg){ System.out.println("hello long"); } // 測試程式碼 public static void main(String[] args) { sayHello('a'); } } // 執行結果 hello char
- 因為
‘a’
是一個char
型別資料(即靜態型別是char
),所以會選擇引數型別為char
的過載方法。 - 若註釋掉
sayHello(char arg)
方法,那麼會輸出
hello int
- 因為
‘a’
除了可代表字串,還可代表數字97。因此當沒有最合適的
sayHello(char arg)
方式進行過載時,會選擇第二合適(第二優先順序)的方法過載,即sayHello(int arg)
- 總結:當沒有最合適的方法進行過載時,會選優先順序第二高的的方法進行過載,如此類推。
- 優先順序順序為:
char>int>long>float>double>Character>Serializable>Object>...
- 其中
...
為變長引數,將其視為一個數組元素。變長引數的過載優先順序最低。 - 因為
char
轉型到byte
或short
的過程是不安全的,所以不會選擇引數型別為byte
或short
的方法進行過載,故優先順序列表裡也沒有。
特別注意
- 上面講解的主要是 基本資料型別 的優先順序匹配問題
- 若是引用型別,則根據 繼承關係 進行優先順序匹配
注意只跟其編譯時型別(即靜態型別)相關
3. 動態分派
-
定義
根據 變數的動態型別 進行方法分派 的 行為
即根據 變數的動態型別 確定執行哪個方法
-
應用場景
方法重寫(
Override
) -
例項說明
// 定義類 class Human { public void sayHello(){ System.out.println("Human say hello"); } } // 繼承自 抽象類Human 並 重寫sayHello() class Man extends Human { @Override protected void sayHello() { System.out.println("man say hello"); } } class Woman extends Human { @Override protected void sayHello() { System.out.println("woman say hello"); } } // 測試程式碼 public static void main(String[] args) { // 情況1 Human man = new man(); man.sayHello(); // 情況2 man = new Woman(); man.sayHello(); } } // 執行結果 man say hello woman say hello // 原因解析 // 1. 方法重寫(Override) = 動態分派 = 根據 變數的動態型別 確定執行(重寫)哪個方法 // 2. 對於情況1:根據變數(Man)的動態型別(man)確定呼叫man中的重寫方法sayHello() // 3. 對於情況2:根據變數(Man)的動態型別(woman)確定呼叫woman中的重寫方法sayHello()
特別注意
對於程式碼中:
Human man = new Man(); man = new Woman(); man.sayHello(); // man稱為執行sayHello()方法的所有者,即接受者。
-
invokevirtual
指令執行的第一步 = 確定接受者的實際型別 -
invokevirtual
指令執行的第二步 = 將 常量池中 類方法符號引用 解析到不同的直接引用上
第二步即方法重寫( Override
)的本質
4. 二者區別

示意圖
5. 總結
-
本文全面講解方法分派的型別 & 過程
-
在接下來的日子,我會推出一系列講解
JVM
的文章,具體如下;感興趣可持續關注 Carson_Ho的安卓開發筆記

示意圖