天天看點

java8學習:預設方法不斷演進的API不同類型的相容:二進制,源代碼和函數行為概述預設方法預設方法的使用模式解決沖突的規則菱形繼承問題

内容來自《 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");
        }
    }           
  • 如上隻要有一個接口有抽象方法,那麼我們就還得按照接口中抽象方法必須實作的規則來
  • 是以解決所有可能的沖突隻需要三點
    • 類或父類中顯示聲明的方法,其優先級高于任何預設方法,即自己實作的比預設的優先級高
    • 如果上面的無法判斷,方法簽名又沒有差別,那麼選擇提供最具體實作的預設方法的接口,即用接口的最具體的子類的實作
    • 如果依舊有沖突,那麼就隻能在本類中重寫覆寫預設方法了