Java和C++中多態(tài)的實現(xiàn)方式

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

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

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

Base *b = new Derived;

b->f();

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

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

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

invokevirtual:用于調用實例方法,會根據(jù)對象的實際類型進行調用。

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

invokeinterface:調用接口的方法。

invokestatic:調用類方法。

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

1.?通過對象中類指針找到其類信息,然后在方法表中根據(jù)方法簽名找到該方法。

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

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容