Java8 高效編程——解決衝突的規則

明:本案例來自 《Java8 實戰》書籍,有需要的朋友到本書的朋友到相關網站購買

電子書的話本人百度網盤提供PDF:(鏈接失效請留言)

鏈接: https://pan.baidu.com/s/1rOji5sj0cOADI2l5dMuHqA 提取碼: efxc

解決下載限速問題查看這篇文章:https://blog.csdn.net/love_moon821/article/details/88896665

解決衝突的規則

我們知道Java語言中一個類只能繼承一個父類,但是一個類可以實現多個接口。隨着默認方法在Java 8中引入,有可能出現一個類繼承了多個方法而它們使用的卻是同樣的函數簽名。這種情況下,類會選擇使用哪一個函數?在實際情況中,像這樣的衝突可能極少發生,但是一旦發生這樣的狀況,必須要有一套規則來確定按照什麼樣的約定處理這些衝突。這一節中,我們會介紹Java編譯器如何解決這種潛在的衝突。我們試圖回答像“接下來的代碼中,哪一個hello方法是被C類調用的”這樣的問題。

注意,接下來的例子主要用於說明容易出問題的場景,並不表示這些場景在實際開發過程中會經常發生。

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 class C implements B, A {
public static void main(String... args) {

//猜 猜 打 印 輸 出的是什麼?
new C().hello();
}
}

解決問題的三條規則

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

  • (1) 類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。
  • (2) 如果無法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時,優先選擇擁有最具體實現的默認方法的接口,即如果B繼承了A,那麼B就比A更加具體。
  • (3) 最後,如果還是無法判斷,繼承了多個接口的類必須通過顯式覆蓋和調用期望的方法,顯式地選擇使用哪一個默認方法的實現。

例子中C類同時實現了B接口和A接口,而這兩個接口恰巧又都定義了名爲hello的默認方法。另外, B繼承自A。圖9-5是這個場景的UML圖。

編譯器會使用聲明的哪一個hello方法呢?按照規則(2),應該選擇的是提供了最具體實現的默認方法的接口。由於B比A更具體,所以應該選擇B的hello方法。所以,程序會打印輸出“Hellofrom B”。

現在,我們看看如果C像下面這樣(如圖9-6所示)繼承自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”。

如果D現在顯式地覆蓋了從A接口中繼承的hello方法。你認爲現在的輸出會是什麼呢?

public class D implements A{
void hello(){
System.out.println("Hello from D");
}
}
public class C extends D implements B, A {
public static void main(String... args) {
new C().hello();
}
}

由於依據規則(1),父類中聲明的方法具有更高的優先級,所以程序會打印輸出“Hello from D”。
注意, D的聲明如下:

public abstract class D implements A {
public abstract void hello();
}

這樣的結果是,雖然在結構上,其他的地方已經聲明瞭默認方法的實現, C還是必須提供自己的hello方法。

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

到目前爲止,你看到的這些例子都能夠應用前兩條判斷規則解決。讓我們更進一步,假設B不再繼承A(如圖9-7所示):

public interface A {
void hello() {
System.out.println("Hello from A");
}
}
public interface B {
void hello() {
System.out.println("Hello from B");
}
}
public class C implements B, A { }

這時規則(2)就無法進行判斷了,因爲從編譯器的角度看沒有哪一個接口的實現更加具體,兩個都差不多。 A接口和B接口的hello方法都是有效的選項。所以, Java編譯器這時就會拋出一個編譯錯誤,因爲它無法判斷哪一個方法更合適:“Error: class C inherits unrelated defaults for hello()from types B and A.

衝突的解決

解決這種兩個可能的有效方法之間的衝突,沒有太多方案;你只能顯式地決定你希望在C中使用哪一個方法。爲了達到這個目的,你可以覆蓋類C中的hello方法,在它的方法體內顯式地調用你希望調用的方法。 Java 8中引入了一種新的語法.super.m(…),其中X是你希望調用的m方法所在的父接口。舉例來說,如果你希望C使用來自於B的默認方法,它的調用方式看起來就如下所示:

public class C implements B, A {
void hello(){

//顯式地選擇調用接口B中的方法
B.super.hello();
}
}

菱形繼承問題

public interface A{
default void hello(){
System.out.println("Hello from A");
}
}
public interface B extends A { }
public interface C extends A { }
public class D implements B, C {
public static void main(String... args) {

//猜 猜 打 印 輸 出的是什麼?
new D().hello();
}
}

圖9-8以UML圖的方式描述了出現這種問題的場景。這種問題叫“菱形問題”,因爲類的繼承關係圖形狀像菱形。這種情況下類D中的默認方法到底繼承自什麼地方 ——源自B的默認方法,還是源自C的默認方法?實際上只有一個方法聲明可以選擇。只有A聲明瞭一個默認方法。由於這個接口是D的父接口,代碼會打印輸出“Hello from A”。

現在,我們看看另一種情況,如果B中也提供了一個默認的hello方法,並且函數簽名跟A中的方法也完全一致,這時會發生什麼情況呢?根據規則(2),編譯器會選擇提供了更具體實現的接口中的方法。由於B比A更加具體,所以編譯器會選擇B中聲明的默認方法。如果B和C都使用相同的函數簽名聲明瞭hello方法,就會出現衝突,正如我們之前所介紹的,你需要顯式地指定使
用哪個方法。

順便提一句,如果你在C接口中添加一個抽象的hello方法(這次添加的不是一個默認方法),會發生什麼情況呢?你可能也想知道答案。

public interface C extends A {
void hello();
}

這個新添加到C接口中的抽象方法hello比由接口A繼承而來的hello方法擁有更高的優先級,因爲C接口更加具體。因此,類D現在需要爲hello顯式地添加實現,否則該程序無法通過編譯。
如果一個類的默認方法使用相同的函數簽名繼承自多個接口,解決衝突的機制其實相當簡單。你只需要遵守下面這三條準則就能解決所有可能的衝突。

  1. 首先,類或父類中顯式聲明的方法,其優先級高於所有的默認方法。
  2. 如果用第一條無法判斷,方法簽名又沒有區別,那麼選擇提供最具體實現的默認方法的接口。
  3. 最後,如果衝突依舊無法解決,你就只能在你的類中覆蓋該默認方法,顯式地指定在你的類中使用哪一個接口中的方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章