(JVM)Java虛擬機:手把手帶你深入解析 - 靜態分派 & 動態分派原理

前言

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

目錄

示意圖


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. 靜態分派

  • 定義
    根據 變量的靜態類型 進行方法分派 的 行爲
  1. 即根據 變量的靜態類型 確定執行哪個方法
  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. 靜態分派的優先級匹配問題

  • 問題描述:
  1. 背景
    現需要進行靜態分派
  2. 問題
    程序中 沒有顯示指定 靜態類型
  3. 解決方案
    程序會根據 靜態類型的優先級 從而選擇 優先的靜態類型進行方法分配。
  • 實例說明
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)
  • 總結:當沒有最合適的方法進行重載時,會選優先級第二高的的方法進行重載,如此類推。
  1. 優先級順序爲:char>int>long>float>double>Character>Serializable>Object>...
  2. 其中...爲變長參數,將其視爲一個數組元素。變長參數的重載優先級最低。
  3. 因爲 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. 二者區別

示意圖


5. 總結

  • 本文全面講解方法分派的類型 & 過程

  • 在接下來的日子,我會推出一系列講解JVM的文章,具體如下;感興趣的同學可以繼續關注本人運營的carsonho的CSDN技術博客
    示意圖


請幫頂 / 評論點贊!因爲你的鼓勵是我寫作的最大動力!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章