首頁 > 軟體

java JVM方法分派模型靜態分派動態分派全面講解

2022-06-23 18:00:27

前言

  • 瞭解 行為方法分派 有利於在行為分派時時進行一些功能操作
  • 本文全面講解行為分派的型別:靜態 & 動態行為分派,希望你們會喜歡。

目錄結構圖

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) = 靜態分派 = 根據 變數的靜態型別 確定執行(過載)哪個方法
  • 所以上述的方法執行時,是根據變數(manwoman)的靜態型別(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 轉型到 byteshort 的過程是不安全的,所以不會選擇引數型別為byteshort的方法進行過載,故優先順序列表裡也沒有。

特別注意

  • 上面講解的主要是 基本資料型別的優先順序匹配問題
  • 若是參照型別,則根據 繼承關係 進行優先順序匹配

注意只跟其編譯時型別(即靜態型別)相關

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. 二者區別

 總結

本文全面講解方法分派的型別 & 過程,更多關於java JVM靜態動態分派模型的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com