解決預設方法衝突的規則

Philip發表於2018-09-05

我們知道Java語言中一個類只能繼承一個父類,但是一個類可以實現多個介面。隨著預設方法在Java8中引入,有可能出現一個類繼承了多個方法而它們使用的卻是同樣的函式簽名。這種情況下,類會選擇使用哪一個函式?在實際情況中,像這樣的衝突可能極少發生,但是一旦發生這樣的情況,必須要有一套規則來確定按照什麼樣的約定處理這些衝突。

解決問題的三條規則

如果一個類使用相同的函式簽名從多個地方(比如另一個類或介面)繼承了方法,通過三條規則可以進行判斷。

  1. 類中的方法優先順序最高。類或父類中宣告的方法的優先順序高於任何宣告為默>認方法的優先順序。
  2. 如果無法依據第一條判斷,那麼子介面的優先順序更高:函式簽名相同時,優>先選擇擁有最具體實現的預設方法的介面,即如果B繼承了A,那麼B就比A更加具體。
  3. 最後,如果還是無法判斷,繼承了多個介面的類必須通過顯式覆蓋和呼叫期>望的方法,顯式地選擇使用哪一個預設方法的實現。

我保證,這就是你需要知道的全部!

選擇提供了最具體實現的預設方法的介面

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}
public interface B extends A {
    default void hello() {
        System.out.println("Hello from B");
    }
}
public interface C implements B, A {
    public static void main(String...args) {
        new C().hello();
    }
}
複製程式碼

編譯器會使用宣告的哪一個hello方法呢?按照規則(2),應該選擇的是提供了最具體實現的預設方法的介面。由於B比A更具體,所以應該選擇B的hello方法。所以程式列印輸入“Hello from B”。
現在,我們看看如果C像下面那樣繼承自D,會發生什麼情況:

public class D implements A{}

public class C extends D implements B, A {
    public static void main(String...args) {
        new C().hello();
    }
}
複製程式碼

依據規則(1),類中宣告的方法具有更高的優先順序。D並未覆蓋hello方法,可是它實現了介面A。所以它擁有了介面A的預設方法。規則(2)說如果類或父類沒有對應的方法,那麼就應該選擇提供了最具體實現的介面中的方法。因此,編譯器會在介面A和介面B的hello方法之間做選擇。由於B更加具體,所以程式會再次列印輸入“Hello from B”。

衝突及如何顯式地消除歧義

到目前為止,你看到的這些例子都能夠應用前兩條判斷規則解決。讓我們更進一步,假設B不再繼承A:

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}
public interface B {
    default void hello() {
        System.out.println("Hello from B");
    }
}
複製程式碼

這是規則(2)就無法進行判斷了,因為從編譯器的角度看沒有哪一個介面的實現更加具體,兩個都差不多。A介面和B介面的hello方法都是有效的選項。所以Java編譯器這是就會丟擲一個編譯錯誤,因為它無法判斷哪一個方法更合適:“Error:class C inherits unrelated default for hello() from types B and A.”
解決這種兩個可能的有效方法之間的衝突,沒有太多方案;你只能顯式地決定你希望在C中使用哪一個方法。為了達到這個目的,你可以覆蓋類C中的hello方法,在它的方法體內顯式地呼叫你希望呼叫的方法。Java8中引入了一種新的語法X.super.m(...),其中X是你希望呼叫的m方法所在的父介面。舉例來說,如果你希望C使用來自於B的預設方法,它的呼叫方式看起來就如下所示:

public class C implements B, A {
    void hello() {
        B.super.hello();
    }
}
複製程式碼

相關文章