[英]How does polymorphism in Java work for this general case (method with parameter)?
我有一般情況的代碼:
public class A {
public String show(A obj) {
return ("A and A");
}
}
public class B extends A {
public String show(B obj) {
return ("B and B");
}
public String show(A obj) {
return ("B and A");
}
}
public class C extends B {
}
public class Test {
public static void main(String[] args) {
A a = new B();
B b = new B();
C c = new C();
System.out.println("1--" + a.show(b));
System.out.println("2--" + a.show(c));
}
}
結果是:
1--B and A
2--B and A
我知道Java中存在從高到低的優先級鏈:
this.show(O), super.show(O), this.show((super)O), super.show((super)O)
我的理解如下:
在這段代碼中:
A a = new B()
發生了一場騷亂。 A是父類引用,B是子父類引用。 編譯並運行代碼時,子父類引用確定如何選擇方法。 在這種情況下,選擇B類中的show(A)
。
還需要多態性必須滿足:所選方法應包含在父類定義中。
有人可以對顯示的結果做出更詳細的解釋嗎?
為了得到結果B and A
兩次的原因,你需要知道這有兩個部分:編譯和運行時。
匯編
遇到語句a.show(b)
,編譯器會采取以下基本步驟:
a
)上調用該方法的對象並獲取其聲明的類型。 這種類型是A
A
類及其所有超類型中,列出名為show
的所有方法。 編譯器只會找到show(A)
。 它沒有查看B
或C
中的任何方法。 b
)最匹配的方法(如果有)。 show(A)
將接受b
,因此選擇此方法。 傳遞c
的第二個調用也會發生同樣的事情。 前兩個步驟是相同的,第三步將再次找到show(A)
因為只有一個,它也匹配參數c
。 因此,對於您的兩個調用,其余過程都是相同的。
一旦編譯器弄清楚它需要什么方法,它將創建一個字節碼指令invokevirtual
,並將解析后的方法show(A)
作為它應該調用的方法(如Eclipse中所示,打開.class
):
invokevirtual org.example.A.show(org.example.A) : java.lang.String [35]
運行
運行時,當它最終到達invokevirtual
時,還需要執行幾個步驟。
a
。 a = new B()
,此類型為B
B
並嘗試找到方法show(A)
。 由於B
覆蓋它,因此找到此方法。 如果不是這種情況,它會查找超類( A
和Object
),直到找到這樣的方法。 重要的是要注意它只考慮show(A)
方法,例如。 從未考慮過來自B
show(B)
。 B
調用方法show(A)
,給出String
B and A
作為結果。 有關這方面的更多細節在invokevirtual
的規范中給出:
如果已解析的方法不是簽名多態(第2.9節),則invokevirtual指令如下進行。
設C為objectref的類。 要調用的實際方法由以下查找過程選擇:
如果C包含一個覆蓋(§5.4.5)已解析方法的實例方法m的聲明,則m是要調用的方法,並且查找過程終止。
否則,如果C具有超類,則使用C的直接超類遞歸地執行相同的查找過程; 要調用的方法是遞歸調用此查找過程的結果。
否則,引發AbstractMethodError。
對於您的示例, objectref
是a
,其類是B
並且已解析的方法是來自invokevirtual
( show(A)
來自A
)
tl:dr - 編譯時確定要調用的方法,運行時確定從哪里調用它。
我認為你的問題涉及另一個主題 - 區分對象和參考。 來自Certified Professional SE 8 Programmer II:在Java中,所有對象都通過引用訪問,因此作為開發人員,您永遠無法直接訪問對象本身的內存。 但是,從概念上講,您應該將對象視為存儲在內存中的實體,由Java運行時環境分配。 無論您在內存中對象的引用類型如何,對象本身都不會更改。 例如,由於所有對象都繼承java.lang.Object,因此可以將它們全部重新分配給java.lang.Object,如以下示例所示:
Lemur lemur = new Lemur();
Object lemurAsObject = lemur;
盡管已經為Lemur對象分配了具有不同類型的引用,但對象本身並未更改,並且仍然作為內存中的Lemur對象存在。 那么,改變的是我們使用lemurAsObject引用訪問Lemur類中的方法的能力。 如果沒有明確的回歸Lemur,正如您將在下一節中看到的那樣,我們無法再訪問對象的Lemur屬性。
我們可以用以下兩個規則來總結這個原則:
在您的示例A a = new B()
, a
是多態引用 - 可以將不同對象指向類層次結構的引用(在這種情況下,它是對類型B
對象的引用,但也可以用作對類A
對象,它是對象層次結構中的最頂層)。
至於你要問的具體行為:
B
在輸出中打印? 將通過引用變量調用哪個特定show(B obj)
方法取決於它在某個時間點保持的對象的引用。 也就是說:如果它持有對B
類對象的引用,那么將調用該類的方法(這是你的情況),但如果它指向類A
的對象,則會調用該對象的引用。 這解釋了為什么B
在輸出中打印。
層次)。
and A
打印在輸出中? 具有相同名稱但簽名不同的子類中的方法稱為方法重載 。 它使用靜態綁定,這意味着在編譯時綁定適當的方法。 編譯器不知道對象的運行時類型。
因此,在這種情況下, A
類的 show(A obj)
將受到約束。 然而,當該方法將在運行時實際上是調用,從類它的實現B
將被調用( show(A obj)
從類B
),這就是為什么你看到B and A
,而不是A and A
輸出。
invokevirutal
參考(執行虛方法時調用的JVM指令) :
如果已解析的方法不是簽名多態(第2.9節),則invokevirtual指令如下進行。
設C為objectref的類。 要調用的實際方法由以下查找過程選擇:
如果C包含一個覆蓋(§5.4.5)已解析方法的實例方法m的聲明,則m是要調用的方法,並且查找過程終止。
否則,如果C具有超類,則使用C的直接超類遞歸地執行相同的查找過程; 要調用的方法是遞歸調用此查找過程的結果。
否則,引發AbstractMethodError。
對於a.show(c)
,適用與B
相同的規則,因為C
沒有重載任何方法,而是直接從B
繼承。
編輯:
逐步解釋為什么a.show(c)
打印B and A
:
a
識別為對類A
對象的objectref
(編譯時) a
是A
類型,所以綁定方法A::show(A obj)
。 a
)上調用show()
方法,運行時會識別a
多態指向B
類對象的引用(因為A a = new B()
)(運行時) C extends B
,運行時會像處理b.show(c)
(或b.show(b)
a.show(c)
一樣處理b.show(c)
),所以在這種情況下使用B::show(A obj)
但是到位的obj
類型的對象B
被使用。 這就是打印“B和A”的原因。 您的參考類型是A
而A
只有一個方法show(A obj)
已在B
重寫並打印B and A
,這就是為什么你總是得到B and A
打印。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.