靜態分派與動態分派


靜態類型,即是變量聲明時的類型

實際類型,變量實例化時采用的類型

 

 

靜態分派

 1 public class StaticDispatch {  
 2     static abstract class Human{  
 3     }  
 4     static class Man extends Human{  
 5     }  
 6     static class Woman extends Human{  
 7     }  
 8     public static void sayHello(Human guy){  
 9         System.out.println("hello,guy!");  
10     }  
11     public static void sayHello(Man guy){  
12         System.out.println("hello,gentlemen!");  
13     }  
14     public static void sayHello(Woman guy){  
15         System.out.println("hello,lady!");  
16     }  
17       
18     public static void main(String[] args) {  
19         Human man=new Man();  
20         Human woman=new Woman();  
21         sayHello(man);  
22         sayHello(woman);  
23     }  
24 }  

  輸出: hello,guy!

  hello,guy!

 

  Human man=new Man();

  我們把“Human”稱為變量的靜態類型,后面的“Man”稱為變量的實際類型

  編譯器在編譯期並不知道一個對象的實際類型是什么

 

  編譯器在重載時是通過參數的靜態類型而不是實際類型作為判定的依據。

  並且靜態類型在編譯期可知,因此,編譯階段,Javac編譯器會根據參數的靜態類型決定使用哪個重載版本。

  所有依賴靜態類型來定位方法執行版本的分派動作稱為靜態分派,其典型應用是方法重載(根據參數的靜態類型來定位目標方法)。

  靜態分派發生在編譯階段,因此確定靜態分派的動作實際上不是由虛擬機執行的。

 

動態分派

 1 public class DynamicDispatch {  
 2     static abstract class Human{  
 3         protected abstract void sayHello();  
 4     }  
 5     static class Man extends Human{   
 6         @Override  
 7         protected void sayHello() {   
 8             System.out.println("man say hello!");  
 9         }  
10     }  
11     static class Woman extends Human{   
12         @Override  
13         protected void sayHello() {   
14             System.out.println("woman say hello!");  
15         }  
16     }   
17     public static void main(String[] args) {  
18           
19         Human man=new Man();  
20         Human woman=new Woman();  
21         man.sayHello();  
22         woman.sayHello();  
23         man=new Woman();  
24         man.sayHello();   
25     }  
26 }  

輸出:
man say hello!
woman say hello!
woman say hello!

 

顯然,這里不可能再根據靜態類型來決定,因為靜態類型同樣是Human的兩個變量man和woman在調用sayHello()方法時執行了不同的行為,並且變量man在兩次調用中執行了不同的方法。導致這個現象的原因很明顯,是這兩個變量的實際類型不同,Java虛擬機是如何根據實際類型來分派方法執行版本的呢?
我們從invokevirtual指令的多態查找過程開始說起,invokevirtual指令的運行時解析過程大致分為以下幾個步驟:

1、找到操作數棧頂的第一個元素所指向的對象的實際類型,記作C
2、如果在類型C中找到與常量中的描述符和簡單名稱相符合的方法,然后進行訪問權限驗證,如果驗證通過則返回這個方法的直接引用,查找過程結束;如果驗證不通過,則拋出java.lang.IllegalAccessError異常。
3、否則未找到,就按照繼承關系從下往上依次對類型C的各個父類進行第2步的搜索和驗證過程。
4、如果始終沒有找到合適的方法,則跑出java.lang.AbstractMethodError異常。

由於invokevirtual指令執行的第一步就是在運行期確定接收者的實際類型,所以兩次調用中的invokevirtual指令把常量池中的類方法符號引用解析到了不同直接引用上,這個過程就是Java語言方法重寫的本質。

我們把這種在運行期根據實際類型確定方法執行版本的分派過程稱為動態分派

 

java語言是一門靜態多分派,動態單分派的語言


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM