前言
- 瞭解 行爲方法分派 有利於在行爲分派時時進行一些功能操作
- 本文全面講解行爲分派的類型:靜態 & 動態行爲分派,希望你們會喜歡。
目錄
1. 知識儲備
1.1 分派
- 定義:確定執行哪個方法 的過程
a. 疑問
有些讀者會問,方法的執行不是取決於代碼設置中的執行對象嗎?爲什麼還要選擇呢?
b. 回答
- 若 一個對象對應於多個方法 時,就需要進行選擇了
- 讀者應該都想到了
Java
中的特性:多態,即重寫 & 重載。下面我會詳細講解。
- 分類:靜態分派 & 動態分派。下面我將詳細講解。
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
的文章,具體如下;感興趣的同學可以繼續關注本人運營的carsonho的CSDN技術博客