Java8系列 (四) 靜態方法和默認方法

  • 2019 年 11 月 2 日
  • 筆記

靜態方法和默認方法

我們可以在 Comparator 介面的源碼中, 看到大量類似下面這樣的方法聲明

    //default關鍵字修飾的默認方法      default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {          return thenComparing(comparingInt(keyExtractor));      }      //Comparator介面中的靜態方法      public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {          return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;      }

其中 thenComparingInt() 就是一個默認方法, 它使用 default 關鍵字修飾。這是Java8引入的新功能: 介面中可以聲明默認方法和靜態方法。

默認方法帶來的多繼承問題

在此之前, Java中的類只支援多重繼承, 不支援多繼承。現在有了默認方法, 你可以以另一種方式來實現類的多繼承行為, 即一個類實現多個介面, 而這幾個介面都有聲明自己的默認方法。

這裡面引發了一個多繼承的問題, 設想一下, 假如一個類從多個介面中繼承了它們聲明的默認方法, 而這幾個默認方法使用的都是相同的函數簽名, 那麼程式運行時, 類會選擇調用哪一個方法呢?

程式碼清單一:

    @Test      public void test2() {          new C().hello();//result: hello from D      }        interface A {          default void hello() {              System.out.println("heelo from A");          }      }        interface B extends A {          default void hello() {              System.out.println("heelo from B");          }      }        class D implements A{          public void hello() {              System.out.println("hello from D");          }      }        class C extends D implements A, B{      }

程式碼清單一的輸出結果是 hello from D,  可以看到, C類的父類D、父介面A、父介面B都定義了一個相同函數簽名的  hello() , 最後實際調用的是父類D中聲明的方法。

程式碼清單二:

    @Test      public void test4() {          new I().hello();//result: heelo from G      }        class I implements G, H { }        interface G extends E {          default void hello() {              System.out.println("heelo from G");          }      }        interface H extends E { }        interface E {          default void hello() {              System.out.println("heelo from E");          }      }

程式碼清單二的輸出結果是 hello from G,  可以看到, I類的父介面G、父介面E都定義了一個相同函數簽名的 hello() ,  最後實際調用的是父介面G中聲明的方法。

程式碼清單三:

    @Test      public void test3() {          new F().hello(); //result: heelo from E      }        interface A {          default void hello() {              System.out.println("heelo from A");          }      }        interface E {          default void hello() {              System.out.println("heelo from E");          }      }        class F implements A, E {          public void hello() {              //這裡介面A和E不再具有繼承關係,需顯式的選擇調用介面E或A中的方法,否則無法通過編譯              E.super.hello();          }      }

程式碼清單三中, 類F必須顯式的覆蓋父介面的 hello() 方法, 否則無法通過編譯器的檢測, 因為編譯器無法確定父介面A和父介面E中的默認方法哪一個優先。

這種情況下, 如果你想調用某個父介面的默認方法, 可以使用  介面名.super.默認方法名 這種方式進行調用。

總結

Java8的新特性: 介面中可以聲明默認方法和靜態方法。

另外, 介面默認方法帶來的多繼承問題, 即如果一個類使用相同的函數簽名從多個地方(比如另一個類或介面)繼承了方法, 通過三條規則可以進行判斷:

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

作者:張小凡
出處:https://www.cnblogs.com/qingshanli/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】。