Java8新增特性,能夠爲接口中添加默認方法,實現這個接口的全部類都會繼承這個方法,這樣看起來,接口和類的界限就有點不明顯了,同時也會帶來多繼承,菱形問題。這樣設計的初衷是什麼?java
重所周知,java8開始支持lambda表達式,能夠把函數當作參數傳遞,最明顯的lambda表達式應用場景莫過於對collection的每個元素應用lambda。若是想爲Collection實現lambda表達式:list.forEach(…);
// 這就是lambda代碼
程序員
首先想到的是爲Collection的父接口iterator添加抽象方法forEach()。然而,對於已經發布的版本,是無法在給接口添加新方法的同時不影響已有的實現。若是添加了,那麼全部的iterator()實現類都要重寫這個方法,若是隻是jre本身的類庫還好說,大量的第三方類庫都使用到了java的優秀集合框架,若是都要重寫,這是不合理的。框架
所以,若是在Java 8裏使用lambda的時候,由於向前兼容的緣由而不能用於collection庫,那有多糟糕啊。ide
因爲上述緣由,引入了一個新的概念。虛擬擴展方法,也即一般說的defender方法, 如今能夠將其加入到接口,這樣能夠提供聲明的行爲的默認實現。函數
簡單的說,Java的接口如今能夠實現方法了。默認方法帶來的好處是能夠爲接口添加新的默認方法,而不會破壞接口的實現。ui
以前:Java接口純粹是契約的集合,是一種程序設計的表達方式。從數據抽象的角度看,可以在不定義class的同時又能夠定義type,將是程序設計中強大而有用的機制。Java接口就是這些純粹的接口組成的數據抽象。Java接口只可以擁有抽象方法,它不涉及任何實現,也不能建立其對象(這一點和抽象類一致)。this
多重繼承模型致使額外的複雜性,其中最著名的是鑽石問題或者叫「討嫌的菱形派生」(Dreadful Diamond onDerivation、DDD)。爲何Java接口可以避免多繼承的複雜性,關鍵在於它僅僅包含abstract方法。然而從設計的角度看,Java接口放棄了多繼承的內在/固有目標,而顯得是一個權宜之計。spa
如今:Java8以前,接口不能升級。由於在接口中添加一個方法,會致使老版本接口的全部實現類的中斷。λ表達式做爲核心出現,爲了配合λ表達式,JDK中Collection庫須要添加新的方法,如forEach(),stream()等,因而引入了默認方法(defender methods,Virtual extension methods)。它是庫/框架設計的程序員的後悔藥。對於之前的遺留代碼,你們都不知道有這個新方法,既不會調用,也不會去實現,如同不存在;編寫新代碼的程序員能夠將它視爲保底的方法體。類型層次中任何符合override規則的方法,優先於默認方法,由於遺留代碼可能正好有一樣的方法存在。.net
默認方法,理論上抹殺了Java接口與抽象類的本質區別——前者是契約的集合,後者是接口與實現的結合體。固然,語法上二者的差異和之前同樣。這就須要程序員來自覺維護二者的本質區別,把默認方法做爲庫、框架向前兼容的手段。設計
默認方法的一個好處:多繼承的著名的是鑽石問題(The Diamond Problem )再次須要關注。於是使之前某些人認爲的「爲了解決多繼承問題而引入接口機制」的說法變成明顯的錯誤——之前也是錯誤的認識。
默認方法的聲明很簡單,直接在接口中把方法聲明爲default,以後再寫方法的實現便可。這樣全部的實現類都會繼承這個方法,問題是他帶來的多繼承問題如何解決?
Iterable中的默認方法:
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
下面是一個簡單的默認方法實現:
public interface A { default void foo(){ System.out.println("Calling A.foo()"); } } public class Clazz implements A { } Clazz clazz = new Clazz(); clazz.foo(); // 調用A.foo()
下面是一個多繼承:
public interface A { default void foo(){ System.out.println("Calling A.foo()"); } } public interface B { default void foo(){ System.out.println("Calling B.foo()"); } } public class Clazz implements A, B { }
這段代碼不能編譯 有如下緣由:
java:class Clazz 從types A到B給foo()繼承了不相關的默認值
爲了修復這個,在Clazz裏咱們不得不手動解決經過重寫衝突的方法,或者調用某個接口中的方法:
public class Clazz implements A, B { public void foo(){} } public class Clazz implements A, B { public void foo(){ A.super.foo(); } }
鑽石問題
狀況一:接口IA有子接口IB一、IB2,而類C implements IB1,IB2
package java8; public class C implements IB1, IB2{ public static void main(String[] a) { new C().m(); } }
(1)若是僅僅A定義默認方法m(),執行IA的默認方法;
(2)若是隻有IB一、IB2其中一個override IA定義的m(),調用「最具體接口」默認方法;
(3)若是IB一、IB2都override IA定義的m(),而C不提供本身的方法體,則編譯錯誤!由於 「最具體接口」默認方法有兩個。此時,C若Override m(),能夠消去編譯錯誤。
(4)類C提供本身的方法體時,能夠提供本身的代碼,也能夠指定調用C implements 的直接父接口的默認方法
小結:多個接口提供默認方法,則「最具體接口」默認方法勝出,可是不得出現多個「最具體接口」。
狀況二:接口IA(或IB1)定義了默認方法m(),類A1相同的方法m(),類C是它們的子類型。
若是類A1提供了實現,按照a simple rule: 「the superclass always wins.」),父類的方法 被調用;
若是類A1不提供實現,即A1中m()爲抽象方法,仍然按照the superclass always wins.類C須要override m(),給出本身的實現。不然,要麼 C聲明爲抽象類,要麼編譯錯誤。
小結:父類有默認方法的等價物,則默認方法如同不存在。
默認方法是對Java語言的有趣補充 – 你能夠把他們看作是lambdas表達式和JDK庫之間的橋樑。默認表達式的主要目標是使標準JDK接口得以進化,而且當咱們最終開始使用Java 8的lambdas表達式時,提供給咱們一個平滑的過渡體驗。誰知道呢,也許未來咱們會在API設計中看到更多的默認方法的應用。
http://www.oschina.net/translate/java-8-explained-default-methods