天天看點

Java8新特性 - 預設接口方法

Java8新特性系列

  • Java8新特性(一) - lambda表達式
  • Java8新特性(二) - Optional類
  • Java8新特性(三) - 流式資料處理
  • Java8新特性(四) - 預設接口方法

一. 引入預設接口方法的背景

  java8可以看做是java版本更新疊代過程中變化最大的一個版本(與時俱進,方能不滅,我們應該感到欣慰),但是經過這麼多年的發展和疊代,java的源碼俨然已是一個龐然大物,要在這樣龐大的體積上大動幹戈,肯定不易。是以當第一次看到java8的預設接口方法的時候,我第一感覺就是這是java的設計人員在填自己之前挖的坑。

  從前幾篇的講解中我們知道java8在現有的接口上添加了許多方法,比如List的

sort(Comparator<? super E> c)

方法。如果按照java8之前接口的設計思路,當給一個接口添加方法聲明的時候,實作該接口的類都必須為該新添加的方法添加相應的實作。考慮相容性,這樣是不可取的,是以說這是一個坑,而新的特性又要求不得不為接口添加一些新的方法,為了兼得魚和熊掌,java8的設計人員提出了預設接口方法的概念。

  這樣說來,預設接口方法似乎是為api的設計人員而開發的,離我們普通開發人員還有些距離,這樣想有點圖森破啦,雖然我們不用去設計jdk,但是我們在日常的開發過程中還是會有提供api給别的業務方調用的需求,當我們在更新我們api的時候,就可以采用預設方法來提供更加進階的功能,同時保持相容性。

二. 預設接口方法的定義

  預設接口方法的定義很簡單,隻要在接口的方法定義前添加一個

default

關鍵字即可,如下:

public interface A {

    /**
     * 預設方法定義
     */
    default void method() {
        System.out.println("This is a default method!");
    }

}
           

  當我們這樣定義一個預設方法之後,所有實作該接口的子類都間接持有了該方法。或者你會和我一樣覺得接口和抽象類越來越像了,确實,不過它們之間還是有如下差别:

1. 一個類隻能繼承一個類,但是可以實作多個接口

2. 抽象類可以定義變量,而接口卻不能
           

  抽象除了解決了我們上面提及到的問題,還具有如下好處:

1. 對于一些不是每個子類都需要的方法,我們給它一個預設實作,進而避免我們在子類中對其無意義的實作(一般我們都會throw new UnsupportedException())

2. 預設方法為java的多重繼承提供了新的途徑(雖然我們隻能繼承一個類,但是我們可以實作多個接口啊,現在接口也可以定義預設方法了)
           

三. 沖突及其解決方法

  因為一個類可以實作多個接口,是以當一個類實作了多個接口,而這些接口中存在兩個或兩個以上方法簽名相同的預設方法時就會産生沖突,java8定義如下三條原則來解決沖突:

1. 類或父類中顯式聲明的方法,其優先級高于所有的預設方法

2. 如果1規則失效,則選擇與目前類距離最近的具有具體實作的預設方法

3. 如果2規則也失效,則需要顯式指定接口
           

下面通過幾個例子加以說明:

  • 例1
public interface A {

    /**
     * 預設方法定義
     */
    default void method() {
        System.out.println("A's default method!");
    }

}

public interface B extends A {

    /**
     * 預設方法定義
     */
    default void method() {
        System.out.println("B's default method!");
    }

}

public class C implements A, B {

    public static void main(String[] args) {
        new C().method();
    }

}

// 輸出:B's default method!
                

此處因為接口B相對于A距離C更近,同時B的method是一個具體的預設實作,依據規則2,是以此處實際上調用的是接口B的預設方法

  • 例2
public class D implements A {
}

public class C extends D implements A, B {

    public static void main(String[] args) {
        new C().method();
    }

}

// 輸出:B's default method!
                

例2在原有接口A、B的基礎上,添加了一個實作接口A的類D,然後類C繼承于D,并實作A和B,此處雖然C離D更近,但因為D的具體實作在A中,是以B中的預設方法還是距離最近的預設實作,依據規則2,此處實際上調用的是B的預設方法。

  • 例3
// A接口不變

public interface B {

    /**
     * 預設方法定義
     */
    default void method() {
        System.out.println("B's default method!");
    }

}

public class C implements A, B {

    @Override
    public void method() {
        // 必須顯式指定
        B.super.method();
    }

    public static void main(String[] args) {
        new C().method();
    }

}
                

例3中接口B不再繼承自接口A,是以此時C中調用預設方法

method()

距離接口A和B的具體實作距離相同,編譯器無法确定,是以報錯,此時需要顯式指定:

B.super.method()