Default 方法

爲何要有Default方法

在Java8發佈之際,有件事情就顯得很是重要,即在不破壞java現有實現架構的狀況下能往接口裏增長新方法。引入Default方法到Java8,正是爲了這個目的:優化接口的同時,避免跟現有實現架構的兼容問題。看下面例子:html

1 List<?> list = ...
2 list.forEach(...);// Lambda code goes here

這樣,即便咱們把Lambda表達式引入到java8中,可是由於不能犧牲向後兼容,而不能夠把Lambda表達式和標準集合類庫結合使用的話,會真的讓人很泄氣。上面的foreach方法既沒有在java.util.List中聲明,也沒有在java.util.Collection中聲明。(若是要使上面代碼生效)容易想到的方案是在現有的接口中新增foreach方法,並在JDK中必要的地方實現foreach。然而,一經發布,要想在某個接口中增長方法,而不修改現該接口現有的實現類,這是不可能作到的。java

爲了解決上述問題,引入了一個新的概念,即虛擬擴展方法(Virtual extension methods),一般也稱之爲 defender 方法,它目前能夠添加到接口中,爲聲明的方法提供默認的實現。api

簡單地說,Java接口如今能夠有非抽象方法了。Default 方法帶來的好處是,往接口新增一個Default 方法,而不破壞現有的實現架構。架構

儘管如此,Default 方法不適合過多使用,可是對於Java集合API的優化升級,並達到無縫地結合Lambda表達式來講,Default 方法是相當重要的特性。函數

Default方法入門

讓咱們從最簡單可行的例子開始:下面代碼定義了接口A,以及實現了接口A的Clazz類:優化

1 public  interface  A {
2     default  void  foo(){
3         System.out.println("Calling A.foo()");
4     }
5 } 
6 public  class  Clazz  implements A {}

上例的客戶端代碼以下:即便Clazz中沒有實現foo(),代碼也能編譯經過。foo()的默認實如今A接口中給出。ui

1 Clazz  clazz= new  Clazz();
2 clazz.foo();// Calling A.foo()

讓咱們用上一個例子來解釋上述狀況,代碼以下所示:當你們第一次據說default方法,一般會問:「若是一個類實現了兩個接口,而這兩個接口中各自定義了一個同名的default方法,會怎麼樣?」this

 1 public  interface A {
 2     default  void  foo(){
 3         System.out.println("Calling A.foo()");
 4     }
 5 } 
 6 public  interface B {
 7     default  void  foo(){
 8         System.out.println("Calling B.foo()");
 9     }
10 } 
11 public class Clazz implements A, B {}

java: class Clazz inherits unrelated defaults for foo() from types A and B上面代碼編譯失敗,報錯以下:spa

爲了修復錯誤,在Clazz中,咱們手工地覆寫掉衝突的方法來處理這個問題,以下所示:.net

1 public  class  Clazz  implements A, B {
2     public  void foo(){}
3 }
1 public  class  Clazz  implements A, B {
2     public  void foo(){
3         A.super.foo();
4     }
5 }

Default方法的實例能夠在JDK8中找到。回到以前集合API的forEach 的例子,咱們能夠在接口java.lang.Iterable中找到forEach 的默認實現:但是,若是咱們就想調用接口A中默認實現的方法foo(),而不用本身實現的,該怎麼辦?那就要像下面這樣使用A#foo()才行。

 1 @FunctionalInterface  public  interface  Iterable{
 2     Iterator iterator(); 
 3  
 4     default  void  forEach(Consumer<?super T> action){
 5          Objects.requireNonNull(action);
 6         for(T t: this){
 7              action.accept(t);
 8         }
 9     }
10 }

上面的forEach方法使用函數接口java.util.function.Consumer做爲參數,該參數使咱們能傳遞一個Lambda表達式或者方法引用到forEach中,以下所示:

1 List<?> list = ...
2 list.forEach(System.out::println);

方法調用

咱們來看看其實是如何調用default方法的。

從客戶端代碼角度來看,default方法只不過都是初始化的抽象方法。所以default方法也叫-虛擬擴展方法。若是出現上例中的那個類,該類實現了帶default方法的接口,那麼調用default方法的客戶端代碼會在調用端生成invokeinterface 。以下所示

1 A  clazz= new  Clazz();
2 clazz.foo();// invokeinterface foo()
3  
4 Clazz  clazz= new  Clazz();
5 clazz.foo();// invokevirtual foo()

下面是javap的輸出。以防出現上述的default方法衝突問題,因此咱們重寫那個default方法,並代理兩個接口之中某一個的default方法的調用,具體代碼請見下面。invokespecial意思是指 咱們將調用專門的實現邏輯。

1 public  class  Clazz  implements A, B {
2     public  void foo(){
3         A.super.foo();// invokespecial foo()
4     }
5 }

下面是javap的輸出。

 public void foo();

 Code:
  0: aload_0
  1: invokespecial #2    // InterfaceMethodA.foo:()V
  4: return

如上面所示的,invokespecial 指令用來調用接口中定義的方法foo()。這從字節碼的角度看也是新東西,由於先前你進行方法調用,僅是經過指向父類的super而不是指向父接口的。

總結

Default方法加入到java中,這是引人關注的事情。Default方法能夠認爲是Lambda表達式和JDK類庫之間的橋樑。引入Default方法的主要目的是爲了升級標準JDK接口,另外也是爲了咱們最終能在Java8中順暢使用Lambda表達式。

相關文章
相關標籤/搜索