[英]What really happens on inheritance in Java?
假設我有兩個類Parent
和Child
,而Child
繼承自Parent
。 我在Parent
有三種方法,其中兩種是公共的,一種是私有的。
通常我們說所有非私有方法都被繼承到Child
類中,但我對確切發生的事情感到困惑。 Java是否在Child
類中復制了這些方法,還是使用某種引用來維護關系?
class Parent{
// Private method
private void method1(){
System.out.println("In private method of Parent class");
}
void method2(){
// calling private method
method1();
}
void method3(){
// calling private method
method1();
}
}
class Child extends Parent{
}
class MainClass{
public static void main(String[] args){
Child child = new Child();
// calling non-private method which internally calls the private method
child.method2();
}
}
Java是否在Child類中創建了方法的副本,還是使用某種引用來維護關系?
后者。 看一下invokespecial 。 Java遞歸地一次查找一個類的方法。 調用第一個匹配方法。 這就是調用虛擬(默認)方法比調用final
方法慢的原因。
通常,從包含該方法的類繼承時,不會復制方法中的實際代碼。
都不是。
繼承是一種編程語言概念,而不是一種真正的行為。 當您研究已編譯的Child
類時,例如使用javap
,您將找不到與Parent
的三種方法相關的任何工件。 將有Child
具有超類Parent
,但沒有提及繼承的方法,既不作為引用也不作為副本。
當您實際嘗試通過編譯時類型為Child
的變量調用其中一個時, Child
概念上從Parent
繼承方法的事實發揮作用,就像在Test
類中一樣。 然后由編譯器來查找繼承的方法,以便正確編譯包含調用的代碼。 編譯器是否在每次解析調用的目標方法時搜索類層次結構,或者它是否收集某些數據結構中的現有方法以加速后續查找以及它使用哪些數據結構也取決於編譯器。
這個過程比你想象的要復雜得多。 可能存在多個候選者,其中編譯器必須選擇一個候選者,並且它甚至可能由於模糊而失敗。 該過程的結果將是關於調用是否有效以及如果它是有效的,單個目標方法及其實際簽名的決定。
編譯的調用將包含目標方法的名稱和簽名,以及對其調用的引用的編譯時類型的引用,即Child
。 它不包含實際方法從Parent
繼承的信息。 這是故意的。 無論是Child
聲明的方法本身,從繼承了Parent
或覆蓋的方法, Parent
不應該影響調用的代碼Test
,也不編譯的類文件之間的兼容性。
這意味着在運行時,像Test
這樣的類可能包含一個名稱和簽名給定的引用,而Child
的方法沒有存儲在Child
的類文件中,換句話說,JVM也負責制作這個概念。繼承工作。 同時,它必須確保在執行方法調用時覆蓋的概念起作用。
它實現這一點的方式也未明確,為不同的策略留出了空間。 在每次調用時搜索類層次結構都是合法的,但會導致性能不佳。
一種常見的技術是vtable 。 與每個類相關聯的是一個表(數組),其中包含對可用方法的引用,無論是聲明還是繼承。 在初始化子類時,它的表將以超類'表的副本開頭,在末尾附加新方法的條目,並且更改了重寫方法的條目。 在某些時候,方法調用將通過查找適合於指定名稱和簽名的vtable條目的索引來鏈接。 典型的策略是在第一次調用時進行查找。 然后修改指令以引用索引以避免后續查找。 從那時起,后續執行包括獲取對象的運行時類的vtable,並從表的條目中獲取方法引用。
考慮到這一點,你可以說在運行時,繼承通常被實現為某種引用 - 但等待。
像Oracle的JVM這樣的實現能夠進行熱點優化,其中考慮了經常執行的代碼的上下文。 例如,可能存在一個繼承的方法,它本身調用在某些子類中被重寫的方法。 當JVM發現在單個具體子類上經常調用此方法時,它可能會為該特定情況創建優化版本。 然后,方法代碼的副本將成為后續代碼轉換的起點,內聯特定子類的代碼等。
由於此類復雜的實現將使用非優化代碼的引用,而在其他情況下使用優化副本,因此初始答案答案的替代方法可能是:
都。
方法體不會復制到子類的未定義方法體中。 相反,當你打電話
Child child1 = new Child();
child1.method1();
它將查看其層次結構,每次無法找到實現時上升到一個級別。 首先,它將檢查沒有實現的Child。 比父級更高一級,所以它將使用那個。正如@Dante上面正確提到的那樣,這是通過子類的構造函數中的super()調用實現的。 此圖片可能會幫助您獲得更好的圖片:
我認為你的困惑在某種程度上與私人方法的非繼承性有關。所以,我也想解決這個問題。
為什么私人會員不被繼承?
你可以說私有成員不是繼承的,因為Child
無處可以明確地引用值。 即任何像this.value
這樣的代碼都不能在Child
,也不能從某些調用代碼中使用obj.value
(顯然)。 但是,從另一個意義上說,你可以說價值是繼承的。 如果您認為Child
每個實例也是Parent
的實例,那么該對象必須包含Parent
定義的'value'
。 即使Child
類對它一無所知,私有成員名稱值仍然存在於Child
每個實例中。 所以在這個意義上,你可以說在Child
沒有使用“繼承”這個詞繼承了這個值。只記得子類不知道在父類中定義的私有成員。 但是請記住,那些私有成員仍然存在於子類的實例中。
注意:JLS聲明( http://docs.oracle.com/javase/specs/jls/se5.0/html/classes.html#8.2 ):
聲明為private的類的成員不會被該類的子類繼承。 只有聲明為protected或public的類的成員才會被聲明在聲明類之外的包中聲明的子類繼承。
Java不會復制這些方法。繼承的方法僅保留在父類中 。
現在你必須要知道,兒童班如何獲得他們的訪問權限?
答案在於理解super關鍵字 。
super關鍵字用於引用直接父類的對象。超級做的第一件事是初始化父類的對象。這意味着它負責創建父類的對象並用於引用該對象。
超級(); 隱式地是子類的構造函數中的第一個語句,但是您也可以明確地這樣做。
現在重要的部分:
如果您有以下內容 :
super();
不是你在子類構造函數中的第一個語句,然后生成編譯錯誤說:
call to super must be first statement in constructor.
原因是:
因為假定子類具有繼承的方法,但是沒有它們的副本。
繼承的方法基本上與父類一起使用,因此首先創建父類的對象非常重要,這樣無論何時使用以下內容
instance_of_child.method_of_parent();
該方法實際上將從父類的對象調用,該對象已由super(顯式或隱式使用)創建。
這就是為什么你可以擁有子類構造函數的原因:
Child()
{
super();
parent_method();
}
但不喜歡:
Child()
{
parent_method();
super();
}
因為parent_method(), 即繼承方法需要引用由super()提供的父類對象。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.