Java和C++中多態的實現方式

多態是面向對象的最主要的特性之一,是一種方法的動態綁定,實現運行時的類型決定對象的行爲。多態的表現形式是父類指針或引用指向子類對象,在這個指針上調用的方法使用子類的實現版本。多態是IOC、模板模式實現的關鍵。

在C++中通過虛函數表的方式實現多態,每個包含虛函數的類都具有一個虛函數表(virtual table),在這個類對象的地址空間的最靠前的位置存有指向虛函數表的指針。在虛函數表中,按照聲明順序依次排列所有的虛函數。比如:

class Base {
public:
	virtual void f() {
		printf("Base::f()");
	}
	
	virtual void g() {
		printf("Base::g()");
	}
};

class Derived: public Base {
public:
	virtual void f() {
		printf("Derived::f()");
	}
};
上面代碼對應的類佈局:

由於C++在運行時並不維護類型信息,所以在編譯時直接在子類的虛函數表中將被子類重寫的方法替換掉,如上圖的Derived::f(),這個方法會被放到虛函數表中原來父函數在的位置。由於在編譯時就確定了虛函數在虛表中的下標,所以在進行虛函數調用時,直接根據下標進行訪問。比如,調用Derived對象上的f():

Base *b = new Derived;
b->f();
在調用b->f()時,內部會轉化成(*b->vptr[1])(),由於虛函數表需要完成RTII,所以虛函數表的第一個slot存放的是type info,虛函數下標從1開始。實際上,虛函數表記錄了這個類的所有虛函數的具體實現(就是在運行時確切要調用的),編譯時就可以確定,不需要動態查找,效率較高。

而Java中,在運行時會維持類型信息以及類的繼承體系。每一個類會在方法區中對應一個數據結構用於存放類的信息,可以通過Class對象訪問這個數據結構。其中,類型信息具有superclass屬性指示了其超類,以及這個類對應的方法表(其中只包含這個類定義的方法,不包括從超類繼承來的)。而每一個在堆上創建的對象,都具有一個指向方法區類型信息數據結構的指針,通過這個指針可以確定對象的類型。

JVM中用於方法調用的指令包括:

invokevirtual:用於調用實例方法,會根據對象的實際類型進行調用。

invokespecial:需要特殊處理的實例方法,比如:public final方法、私有方法和父類方法等。調用的方法取決於引用的類型。

invokeinterface:調用接口的方法。

invokestatic:調用類方法。

按照上面描述,對於子類覆蓋父類的方法,編譯後,調用指令應該是invokevirtual,調用的方法取決於對象的類型。invokevirtual方法查找的實現方式是:

1. 通過對象中類指針找到其類信息,然後在方法表中根據方法簽名找到該方法。

2. 如果不在當前類,則遞歸查找其父類的方法表直到Object類。

3. 如果找到Object類,也沒有該方法,會拋出NoSuchMethodException異常。

與js、lua等動態語言類似,Java的實現方式依賴於內存中的類型體系信息,存在一個“原型鏈”,是一個完全動態的查找過程,相對於C++而言,效率會低一些,因爲存在一個鏈表遍歷查找的過程。之所以,Java中可以這樣實現,本質上是因爲它是一門虛擬機語言,虛擬機會維持所有的這些類型信息。

發佈了119 篇原創文章 · 獲贊 69 · 訪問量 126萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章