Java8接口中的默認方法

  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

相關文章
相關標籤/搜索