在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 方法是相當重要的特性。函數
讓咱們從最簡單可行的例子開始:下面代碼定義了接口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表達式。