java8學習:默認方法

內容來自《 java8實戰 》,本篇文章內容均爲非盈利,旨爲方便自己查詢、總結備份、開源分享。如有侵權請告知,馬上刪除。
書籍購買地址:java8實戰

  • 在java8中接口引進了靜態方法以及默認方法,通過默認方法,可以爲接口方法提供默認實現,也就是說接口能提供方法的具體實現.因此實現接口的類如果不顯示的提供該方法的具體實現,就會自動繼承默認的實現
  • 同時定義接口以及工具輔助類是java常用的一種模式,工具類定義了與接口實例協作的很多靜態方法,由於靜態方法現在可以存在接口內部,所以代碼中的輔助類就沒有了存在的必要,可以直接把這些靜態方法轉移到接口內部

不斷演進的API

  • 我們來說一下爲什麼會出現默認方法
  • 假如有如下一個接口,你是類庫的設計者,那麼你寫的接口是這樣的
public interface Behavior {
    void eat(String foodName);
}
  • 然後你發佈之後大火,你的用戶是這樣使用的
public class Dog implements Behavior {
    @Override
    public void eat(String foodName) {
        System.out.println("dog " + foodName);
    }
}
  • 之後你收到了很多意見,說這個行爲接口並不豐富,行爲不可能只有吃的行爲,動物可是都有吃喝拉撒的行爲啊!
  • 現在你就遇到了問題,如果你將建議的接口添加入Behavior接口,然後發佈,這時候你的用戶只要更新API的版本,那麼他就會遇到一個大麻煩,那麼就是實現你在接口中定義的所有方法.這對用戶來說是非常不好的.這也是默認方法產生的原因:它可以讓你放心的改進接口,無須擔心遺留代碼的影響,這是因爲實現更新接口的類都會自動的集成一個默認的方法實現

不同類型的兼容:二進制,源代碼和函數行爲

  • 變更對java的影響大體可以分爲三種類型的兼容:二進制級的兼容,源代碼級的兼容,以及函數行爲的兼容.

    • 二進制級的兼容性表示現有的二進制執行文件能無縫持續鏈接和運行,比如,爲接口添加一個方法就是二進制級的兼容,這種方式下,如果添加的新方法不被調用,接口已經實現的方法可以繼續運行,不會出現錯誤
    • 簡單的說,源代碼級的兼容性表示引入變化之後,現有的程序依然能夠成功通過編譯
    • 函數行爲的兼容性表示發生變更後,程序接受同樣的輸入能得到相同的結果

概述默認方法

  • 默認方法就是用default修飾的方法,並像類中聲明的其他方法一樣包含方法體,並且只要類實現了這個包含默認方法的接口,他就會繼承默認方法
  • java8中的抽象類和抽象接口區別:首先一個類只能繼承一個抽象類,但是一個類可以實現多個接口,其次,一個抽象類可以通過實例變量保存一個通用狀態,而接口是不能有實例變量的

默認方法的使用模式

  • 可選模式

    • 平常我們用類實現一個接口,接口中有很多方法需要我們重新定義,如果有用的方法還好,如果我們並用不到的方法,爲了滿足接口方法的實現規則,我們就必須在那放一個空方法實現,這也是多餘的模板代碼,我們可以將這類方法變更爲默認方法以實現不必要的空實現
  • 行爲的多繼承

    • 行爲的多繼承值得是:類只能繼承一個類,但是可以實現多個接口中的方法,這就是所謂的多繼承,現在java8中有了方法的默認實現,那麼我們的類就得到了來自不同接口的實現的功能

解決衝突的規則

  • 一個類實現了多個接口,而多個接口中含有覆蓋實現的方法,那麼類會使用那個接口中的方法呢?如果是多個接口中的方法都是相同的方法簽名呢?

    interface A{
        default void say(){
            System.out.println("A");
        }
    }
    interface B extends A {
        @Override
        default void say() {
            System.out.println("B");
        }
    }
    public class Dog implements B{
        public static void main(String[] args) {
            new Dog().say(); //B
        }
    }
  • 如果一個類使用相同的函數簽名從多個地方繼承了方法,通過三條規則可以進行判斷

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

    interface A{
        default void say(){
            System.out.println("A");
        }
    }
    interface B extends A {
        @Override
        default void say() {
            System.out.println("B");
        }
    }
    class D implements A{
    
    }
    public class Dog extends D implements A,B{
        public static void main(String[] args) {
            new Dog().say(); //B
        }
    }
    • 依照類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級,那麼就會優先選擇D,那麼D並沒有覆蓋掉say方法,但是他默認繼承了A的方法,所以它會調用A的方法,但是他會選擇更具體的實現,那麼就是B,最終也是輸出的B

菱形繼承問題

interface A{
    default void say(){
        System.out.println("A");
    }
}
interface B extends A {

}
interface C extends A{

}
public class Dog implements B,C{
    public static void main(String[] args) {
        new Dog().say(); //A
    }
}
  • 如上,其繼承實現關係類似於菱形,Dog實現了B,C但是BC中只是繼承了A中的默認方法,所以編譯並不會出錯,並且Dog也是使用此默認實現,所以輸出A
  • 如果我們將B加上覆蓋實現呢?

    interface B extends A {
        default void say(){
            System.out.println("B");
        }
    }
  • 現在會輸出B,因爲覆蓋實現的方法更加具體
  • 如果兩個BC接口都覆蓋實現了say方法呢?

    //B接口如上變動
    interface C extends A{
        default void say(){
            System.out.println("C");
        }
    }
  • 這時候我們就會發現,Dog編譯出錯了,因爲BC都有具體實現,並且都是一個級別的,都是具體實現了A的默認方法,Dog就不知道需要調用誰的方法了,這時候我們只能是在Dog中覆蓋這個默認方法的實現了

    public class Dog implements B,C{
        public static void main(String[] args) {
            new Dog().say(); //dog
        }
        @Override
        public void say() {
            System.out.println("dog");
        }
    }
  • 如果BC接口只是定義的與A中say方法簽名一樣的抽象方法呢?

    interface A{
        default void say(){
            System.out.println("A");
        }
    }
    interface B extends A {
        void say();
    }
    interface C extends A{
        default void say(){
            System.out.println("C");
        }
    }
    public class Dog implements B,C{
        public static void main(String[] args) {
            new Dog().say(); //dog
        }
        @Override
        public void say() {
            System.out.println("dog");
        }
    }
  • 如上只要有一個接口有抽象方法,那麼我們就還得按照接口中抽象方法必須實現的規則來
  • 所以解決所有可能的衝突只需要三點

    • 類或父類中顯示聲明的方法,其優先級高於任何默認方法,即自己實現的比默認的優先級高
    • 如果上面的無法判斷,方法簽名又沒有區別,那麼選擇提供最具體實現的默認方法的接口,即用接口的最具體的子類的實現
    • 如果依舊有衝突,那麼就只能在本類中重寫覆蓋默認方法了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章