Java8 新特性之預設介面方法

zhenchao發表於2016-10-09

摘要: 從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()

相關文章