一. 引入預設介面方法的背景
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. 對於一些不是每個子類別都需要的方法,我們給它一個預設實現,從而避免我們在子類別中對其無意義的實現(一般我們都會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()。