JavaRush /Java Blog /Random-TW /Java 8 中的預設方法:它們能做什麼、不能做什麼?
Spitfire
等級 33

Java 8 中的預設方法:它們能做什麼、不能做什麼?

在 Random-TW 群組發布
Peter Verhas於 2014 年 4 月撰寫的文章的翻譯。 Java 8 中的預設方法:它們能做什麼、不能做什麼? - 1來自譯者:術語「預設方法」剛出現在 Java 中,我不確定是否有一個已確定的俄語翻譯。我將使用術語“預設方法”,儘管我認為它並不理想。我邀請您討論更成功的翻譯。

預設方法是什麼

現在,隨著 Java 8 的發布,您可以為介面新增方法,以便介面與實作它的類別保持相容。如果您正在開發一個被從基輔到紐約的許多程式設計師使用的程式庫,這一點非常重要。在 Java 8 之前,如果您在庫中定義了接口,則無法向其中添加方法,否則會面臨運行接口的某些應用程式在更新時崩潰的風險。那麼,在 Java 8 中你可以不再害怕這個了嗎?你不能。 向介面新增預設方法可能會使某些類別無法使用。 讓我們先看看預設方法的好處。在Java 8中,此方法可以直接在介面中實作。(介面中的靜態方法現在也可以實現,但那是另一回事了。)在介面中實現的方法稱為預設方法,並由default關鍵字表示。如果一個類別實作了一個接口,它可以(但不是必須)實作該接口中實現的方法。該類別繼承了預設實作。這就是為什麼在更改類別實作的介面時無需修改類別。

多重繼承?

如果一個類別實作了多個(例如兩個)接口,並且它們實作了相同的預設方法,事情就會變得更加複雜。該類別將繼承哪個方法?答案是否定的。在這種情況下,類別必須實作該方法本身(直接或透過從另一個類別繼承它)。如果只有一個介面有預設方法,而在另一個介面中相同的方法是抽象的,則情況類似。Java 8 試圖遵守紀律並避免出現模稜兩可的情況。如果在多個介面中聲明方法,則該類別不會繼承預設實作 - 您將收到編譯錯誤。儘管如此,如果您的類別已經編譯,您可能不會收到編譯錯誤。Java 8 在這方面還不夠健壯。這是有原因的,我不想討論(例如:Java 版本已經發布,討論的時間已經過去了,總的來說,這不是他們的地方)。
  • 假設您有兩個接口,並且一個類別實作了這兩個接口。
  • 其中一個介面實作了預設方法 m()。
  • 您編譯所有介面和類別。
  • 您可以透過將沒有 m() 方法的介面宣告為抽象方​​法來變更它。
  • 您僅編譯修改後的介面。
  • 開始上課吧。
Java 8 中的預設方法:它們能做什麼、不能做什麼? - 2在這種情況下,類別就可以工作了。您無法使用更新的介面編譯它,但它是使用舊版本編譯的,因此可以工作。現在
  • 使用抽象 m() 方法變更介面並新增預設實作。
  • 編譯修改後的介面。
  • 運行類別:錯誤。
當有兩個介面提供方法的預設實作時,該方法不能在類別中調用,除非該方法由類別本身實作(同樣,無論是自己實作還是從另一個類別繼承)。 Java 8 中的預設方法:它們能做什麼、不能做什麼? - 3類兼容。它可以載入修改後的介面。它甚至可以運行,直到呼叫在兩個介面中都有預設實現的方法為止。

範例程式碼

Java 8 中的預設方法:它們能做什麼、不能做什麼? - 4為了示範上述內容,我為 C.java 類別建立了一個測試目錄,並為檔案 I1.java 和 I2.java 中的介面建立了 3 個子目錄。測試的根目錄包含 C.java 類別的原始程式碼。基底目錄包含適合執行和編譯的介面版本:介面 I1 有一個預設方法 m();I2 介面還沒有任何方法。該類別有一個方法main,因此我們可以執行它來測試它。它檢查是否有任何命令列參數,因此我們可以輕鬆地執行它,無論是否調用m().
~/github/test$ cat C.java
public class C implements I1, I2 {
  public static void main(String[] args) {
    C c = new C();
    if( args.length == 0 ){
      c.m();
    }
  }
}
~/github/test$ cat base/I1.java
public interface I1 {
  default void m(){
    System.out.println("hello interface 1");
  }
}
~/github/test$ cat base/I2.java
public interface I2 {
}
您可以從命令列編譯並運行該類別。
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
相容目錄包含 I2 介面的一個版本,該版本將 m() 方法宣告為抽象方​​法,並且出於技術原因,還包含 I1.java 的未修改副本。
~/github/test$ cat compatible/I2.java

public interface I2 {
  void m();
}
這樣的集合不能用來編譯 C 類別:
~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
       ^
1 error
錯誤訊息非常準確。但是,我們有之前編譯的 C.class,如果我們將介面編譯到相容目錄中,我們將有兩個仍然可用於運行該類別的介面:
~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1
第三個目錄--wrong包含版本I2,它也宣告了方法m()
~/github/test$ cat wrong/I2.java
public interface I2 {
  default void m(){
    System.out.println("hello interface 2");
  }
}
您甚至不必擔心編譯。即使該方法被宣告兩次,該類別仍然可以使用並運行,直到呼叫 m() 方法。這就是我們需要命令列參數的原因:
~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
    at C.m(C.java)
    at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$

結論

當您將程式庫移植到 Java 8 並變更介面以包含預設方法時,您可能不會遇到任何問題。至少,這是 Java 8 庫開發人員在添加功能時所希望的。使用您的庫的應用程式仍在 Java 7 中使用它,其中沒有預設方法。如果多個庫一起使用,就有可能發生衝突。如何避免呢?按照與以前相同的方式設計庫的 API。不要因依賴預設方法的功能而沾沾自喜。他們是最後的手段。仔細選擇名稱以避免與其他介面衝突。讓我們看看使用這個特性的Java開發將會如何發展。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION