1. 程式人生 > >【深入Java虛擬機器(5)】:多型性實現機制—靜態分派與動態分派

【深入Java虛擬機器(5)】:多型性實現機制—靜態分派與動態分派

方法解析

Class檔案的編譯過程中不包含傳統編譯中的連線步驟,一切方法呼叫在Class檔案裡面儲存的都只是符號引用,而不是方法在實際執行時記憶體佈局中的入口地址。這個特性給Java帶來了更強大的動態擴充套件能力,使得可以在類執行期間才能確定某些目標方法的直接引用,稱為動態連線,也有一部分方法的符號引用在類載入階段或第一次使用時轉化為直接引用,這種轉化稱為靜態解析。這在前面的“Java記憶體區域與記憶體溢位”一文中有提到。 靜態解析成立的前提是:方法在程式真正執行前就有一個可確定的呼叫版本,並且這個方法的呼叫版本在執行期是不可改變的。換句話說,呼叫目標在編譯器進行編譯時就必須確定下來,這類方法的呼叫稱為解析。 在Java語言中,符合“編譯器可知,執行期不可變”這個要求的方法主要有靜態方法和私有方法兩大類,前者與型別直接關聯,後者在外部不可被訪問,這兩種方法都不可能通過繼承或別的方式重寫出其他的版本,因此它們都適合在類載入階段進行解析。 Java虛擬機器裡共提供了四條方法呼叫位元組指令,分別是: invokestatic:呼叫靜態方法。 invokespecial:呼叫例項構造器方法、私有方法和父類方法。 invokevirtual:呼叫所有的虛方法。 invokeinterface:呼叫介面方法,會在執行時再確定一個實現此介面的物件。 只要能被invokestatic和invokespecial指令呼叫的方法,都可以在解析階段確定唯一的呼叫版本,符合這個條件的有靜態方法、私有方法、例項構造器和父類方法四類,它們在類載入時就會把符號引用解析為該方法的直接引用。這些方法可以稱為非虛方法(還包括final方法),與之相反,其他方法就稱為虛方法(final方法除外)。這裡要特別說明下final方法,雖然呼叫final方法使用的是invokevirtual指令,但是由於它無法覆蓋,沒有其他版本,所以也無需對方發接收者進行多型選擇。Java語言規範中明確說明了final方法是一種非虛方法。 解析呼叫一定是個靜態過程,在編譯期間就完全確定,在類載入的解析階段就會把涉及的符號引用轉化為可確定的直接引用,不會延遲到執行期再去完成。而分派呼叫則可能是靜態的也可能是動態的,根據分派依據的宗量數(方法的呼叫者和方法的引數統稱為方法的宗量)又可分為單分派和多分派。兩類分派方式兩兩組合便構成了靜態單分派、靜態多分派、動態單分派、動態多分派四種分派情況。 靜態分派

所有依賴靜態型別來定位方法執行版本的分派動作,都稱為靜態分派,靜態分派的最典型應用就是多型性中的方法過載。靜態分派發生在編譯階段,因此確定靜態分配的動作實際上不是由虛擬機器來執行的。下面通過一段方法過載的示例程式來更清晰地說明這種分派機制:

class Human{
} 
class Man extends Human{
}
class Woman extends Human{
}
 
public class StaticPai{
 
 public void say(Human hum){
 System.out.println("I am human");
 }
 public void say(Man hum){
 System.out.println("I am man");
 }
 public void say(Woman hum){
 System.out.println("I am woman");
 }
 
 public static void main(String[] args){
 Human man = new Man();
 Human woman = new Woman();
 StaticPai sp = new StaticPai();
 sp.say(man);
 sp.say(woman);
 }
}

上面程式碼的執行結果如下:

I am human
 I am human

以上結果的得出應該不難分析。在分析為什麼會選擇引數型別為Human的過載方法去執行之前,先看如下程式碼: Human man = new Man(); 我們把上面程式碼中的“Human”稱為變數的靜態型別,後面的“Man”稱為變數的實際型別。靜態型別和實際型別在程式中都可以發生一些變化,區別是靜態型別的變化僅僅在使用時發生,變數本身的靜態型別不會被改變,並且最終的靜態型別是在編譯期可知的,而實際型別變化的結果在執行期才可確定。 回到上面的程式碼分析中,在呼叫say()方法時,方法的呼叫者(回憶上面關於宗量的定義,方法的呼叫者屬於宗量)都為sp的前提下,使用哪個過載版本,完全取決於傳入引數的數量和資料型別(方法的引數也是資料宗量)。程式碼中刻意定義了兩個靜態型別相同、實際型別不同的變數,可見編譯器(不是虛擬機器,因為如果是根據靜態型別做出的判斷,那麼在編譯期就確定了)在過載時是通過引數的靜態型別而不是實際型別作為判定依據的。並且靜態型別是編譯期可知的,所以在編譯階段,Javac編譯器就根據引數的靜態型別決定使用哪個過載版本。這就是靜態分派最典型的應用。 動態分派

動態分派與多型性的另一個重要體現——方法覆寫有著很緊密的關係。向上轉型後呼叫子類覆寫的方法便是一個很好地說明動態分派的例子。這種情況很常見,因此這裡不再用示例程式進行分析。很顯然,在判斷執行父類中的方法還是子類中覆蓋的方法時,如果用靜態型別來判斷,那麼無論怎麼進行向上轉型,都只會呼叫父類中的方法,但實際情況是,根據對父類例項化的子類的不同,呼叫的是不同子類中覆寫的方法,很明顯,這裡是要根據變數的實際型別來分派方法的執行版本的。而實際型別的確定需要在程式執行時才能確定下來,這種在執行期根據實際型別確定方法執行版本的分派過程稱為動態分派。 單分派和多分派

前面給出:方法的接受者(亦即方法的呼叫者)與方法的引數統稱為方法的宗量。但分派是根據一個宗量對目標方法進行選擇,多分派是根據多於一個宗量對目標方法進行選擇。 為了方便理解,下面給出一段示例程式碼:

class Eat{
}
class Drink{
}
 
class Father{
 public void doSomething(Eat arg){
 System.out.println("爸爸在吃飯");
 }
 public void doSomething(Drink arg){
 System.out.println("爸爸在喝水");
 }
}
 
class Child extends Father{
 public void doSomething(Eat arg){
 System.out.println("兒子在吃飯");
 }
 public void doSomething(Drink arg){
 System.out.println("兒子在喝水");
 }
}
 
public class SingleDoublePai{
 public static void main(String[] args){
 Father father = new Father();
 Father child = new Child();
 father.doSomething(new Eat());
 child.doSomething(new Drink());
 }
}

執行結果應該很容易預測到,如下: 爸爸在吃飯 兒子在喝水 我們首先來看編譯階段編譯器的選擇過程,即靜態分派過程。這時候選擇目標方法的依據有兩點:一是方法的接受者(即呼叫者)的靜態型別是Father還是Child,二是方法引數型別是Eat還是Drink。因為是根據兩個宗量進行選擇,所以Java語言的靜態分派屬於多分派型別。 再來看執行階段虛擬機器的選擇,即動態分派過程。由於編譯期已經了確定了目標方法的引數型別(編譯期根據引數的靜態型別進行靜態分派),因此唯一可以影響到虛擬機器選擇的因素只有此方法的接受者的實際型別是Father還是Child。因為只有一個宗量作為選擇依據,所以Java語言的動態分派屬於單分派型別。 根據以上論證,我們可以總結如下:目前的Java語言(JDK1.6)是一門靜態多分派、動態單分派的語言。

在這裡給大家提供一個學習交流的平臺,java架構師群: 867748702

具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加群。

在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加群。

如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的可以加群。

加Java架構師進階交流群獲取Java工程化、高效能及分散式、高效能、深入淺出。高架構。 效能調優、Spring,MyBatis,Netty原始碼分析和大資料等多個知識點高階進階乾貨的直播免費學習許可權 都是大牛帶飛 讓你少走很多的彎路的 群號是: 867748702對了 小白勿進 最好是有開發經驗

注:加群要求

1、具有工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿里Java高階大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!