簡體   English   中英

Java中的多態如何適用於這種一般情況(帶參數的方法)?

[英]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) ,編譯器會采取以下基本步驟:

  1. 查看在( a )上調用該方法的對象並獲取其聲明的類型。 這種類型是A
  2. A類及其所有超類型中,列出名為show的所有方法。 編譯器只會找到show(A) 它沒有查看BC中的任何方法。
  3. 從找到的方法列表中,選擇與參數( 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時,還需要執行幾個步驟。

  1. 獲取調用該方法的對象(此時已經在堆棧中),這是a
  2. 查看此對象的實際運行時類型。 由於a = new B() ,此類型為B
  3. 查看B並嘗試找到方法show(A) 由於B覆蓋它,因此找到此方法。 如果不是這種情況,它會查找超類( AObject ),直到找到這樣的方法。 重要的是要注意它只考慮show(A)方法,例如。 從未考慮過來自B show(B)
  4. 運行時現在將從B調用方法show(A) ,給出String B and A作為結果。

有關這方面的更多細節在invokevirtual規范中給出:

如果已解析的方法不是簽名多態(第2.9節),則invokevirtual指令如下進行。

設C為objectref的類。 要調用的實際方法由以下查找過程選擇:

如果C包含一個覆蓋(§5.4.5)已解析方法的實例方法m的聲明,則m是要調用的方法,並且查找過程終止。

否則,如果C具有超類,則使用C的直接超類遞歸地執行相同的查找過程; 要調用的方法是遞歸調用此查找過程的結果。

否則,引發AbstractMethodError。

對於您的示例, objectrefa ,其類是B並且已解析的方法是來自invokevirtualshow(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屬性。

我們可以用以下兩個規則來總結這個原則:

  1. 對象的類型確定內存中對象中存在哪些屬性。
  2. 對象引用的類型確定Java程序可以訪問哪些方法和變量。

在您的示例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

  1. 編譯器將對象a識別為對類A對象的objectref (編譯時)
  2. 因為aA類型,所以綁定方法A::show(A obj)
  3. 當代碼實際執行時(即在對象a )上調用show()方法,運行時會識別a多態指向B類對象的引用(因為A a = new B() )(運行時)
  4. 因為C extends B ,運行時會像處理b.show(c) (或b.show(b) a.show(c)一樣處理b.show(c) ),所以在這種情況下使用B::show(A obj)但是到位的obj類型的對象B被使用。 這就是打印“B和A”的原因。

您的參考類型是AA只有一個方法show(A obj)已在B重寫並打印B and A ,這就是為什么你總是得到B and A打印。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM