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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM