1. 程式人生 > >Java8系列 (四) 靜態方法和預設方法

Java8系列 (四) 靜態方法和預設方法

靜態方法和預設方法

我們可以在 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/
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】。