解析Java中的默認方法
為什么有默認方法?
Java 8 就要來臨,盡管發(fā)布期限已經(jīng)被推遲, 我們?nèi)苑浅4_信在它最終發(fā)布的時候會支持lambdas 表達式。 前面提到過,我們之前關(guān)于這個主題已經(jīng)討論了不少,不過,lambdas表達式并不是Java 8中唯一改變的游戲規(guī)則。
假設(shè)Java 8 已經(jīng)發(fā)布并且包含了lambda?,F(xiàn)在你打算用一下lambda,最明顯的應(yīng)用場景莫過于對collection的每一個元素應(yīng)用lambda。
List<?> list = … list.forEach(…); // 這就是lambda代碼
在java.util.List或者java.util.Collection接口里都找不到forEach的定義。通常能想到的解決辦法是在JDK里給相關(guān)的接口添加新的方法及實現(xiàn)。然而,對于已經(jīng)發(fā)布的版本,是沒法在給接口添加新方法的同時不影響已有的實現(xiàn)。
因此,如果在Java 8里使用lambda的時候,因為向前兼容的原因而不能用于collection庫,那有多糟糕啊。
由于上述原因,引入了一個新的概念。虛擬擴展方法,也即通常說的defender方法, 現(xiàn)在可以將其加入到接口,這樣可以提供聲明的行為的默認實現(xiàn)。
簡單的說,Java的接口現(xiàn)在可以實現(xiàn)方法了。默認方法帶來的好處是可以為接口添加新的默認方法,而不會破壞接口的實現(xiàn)。
在我看來,這并非那種每天都會用到的Java特性,但是它絕對能讓Java的Collections API可以很自然的使用lambda。
最簡單的例子
讓我們看一個最簡單的例子:一個接口A,Clazz類實現(xiàn)了接口A。
public interface A { default void foo(){ System.out.println("Calling A.foo()"); } } public class Clazz implements A { }
代碼是可以編譯的,即使Clazz類并沒有實現(xiàn)foo()方法。在接口A中提供了foo()方法的默認實現(xiàn)。
使用這個例子的客戶端代碼:
Clazz clazz = new Clazz(); clazz.foo(); // 調(diào)用A.foo()
多重繼承?
有一個常見的問題:人們會問 當(dāng)他們第一次聽到關(guān)于默認方法的新的特性時 “如果一個類實現(xiàn)了兩個接口,并且兩個接口都用相同的簽名定義了默認方法,這該怎么辦?”讓我們用先前的例子來展示這個解決方案:
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()繼承了不相關(guān)的默認值
為了修復(fù)這個,在Clazz里我們不得不手動解決通過重寫沖突的方法:
public class Clazz implements A, B { public void foo(){} }
但是如果我們想從接口A中調(diào)用默認實現(xiàn)方法foo(),而不是實現(xiàn)我們自己的方法,該怎么辦呢?這是有可能的,引用A中的foo(),如下所示:
public class Clazz implements A, B { public void foo(){ A.super.foo(); } }
現(xiàn)在我不能十分確信我喜歡這個最終方案。也許它比在簽名里聲明默認方法的實現(xiàn)更為簡練,正如在默認方法規(guī)范的第一手稿里所聲明的:
public class Clazz implements A, B { public void foo() default A.foo; }
但是這確實更改了語法,難道不是嗎?它看起來更像一個接口的方法聲明而不是實現(xiàn)。假若接口A和接口B定義了許多相互沖突的默認方法,而我愿意使用所有接口A的默認方法解決沖突,那又如何呢?目前我不得不一個接著一個的解決沖突,改寫每一對沖突的方法。這可能需要大量的工作和書寫大量的模板代碼。
我估計解決沖突的方法需要進行大量的討論,不過看起來創(chuàng)建者決定接受這無法避免的災(zāi)難。
真實的例子
默認方法實現(xiàn)的真實例子可以在 JDK8早期打的包中找到。回到集合的forEach方法的例子中, 我們可以發(fā)現(xiàn)在java.lang.Iterable接口中,它的默認實現(xiàn)如下:
@FunctionalInterface public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } }
forEach 使用了一個java.util.function.Consumer功能接口類型的參數(shù),它使得我們可以傳入一個lambda表達式或者一個方法引用,如下:
List<?> list = … list.forEach(System.out::println);
方法調(diào)用
讓我們看一下實際上是如何調(diào)用默認的方法的。如果你不熟悉這個問題,那么你可能有興趣閱讀一下Rebel實驗室有關(guān)Java字節(jié)的報告。
從客戶端代碼的視角來看,默認的方法僅僅是常見的虛擬方法。因此名字應(yīng)該是虛擬擴展方法。因此對于把默認方法實現(xiàn)為接口的簡單例子類來說,客戶端代碼將在調(diào)用默認方法的地方自動調(diào)用接口。
A clazz = new Clazz(); clazz.foo(); // invokeinterface foo() Clazz clazz = new Clazz(); clazz.foo(); // invokevirtual foo()
如果默認方法的沖突已經(jīng)解決,那么當(dāng)我們修改默認方法并指定調(diào)用其中一個接口時候,invokespecial將給我們指定具體調(diào)用哪個接口的實現(xiàn)。
public class Clazz implements A, B { public void foo(){ A.super.foo(); // invokespecial foo() } }
下面是javap的輸出:
public void foo(); Code: 0: aload_0 1: invokespecial #2 // InterfaceMethod A.foo:()V 4: return
正如你看到的:invokespecial指令用來調(diào)用接口方法foo()。從字節(jié)碼的視角來看,這仍是新鮮的事情,因為以前你只能通過指向一個類(父類)的而不是指向一個接口的super來調(diào)用方法。
最后…
默認方法是對Java語言的有趣補充 – 你可以把他們看做是lambdas表達式和JDK庫之間的橋梁。默認表達式的主要目標是使標準JDK接口得以進化,并且當(dāng)我們最終開始使用Java 8的lambdas表達式時,提供給我們一個平滑的過渡體驗。誰知道呢,也許將來我們會在API設(shè)計中看到更多的默認方法的應(yīng)用。
相關(guān)文章
springBoot+webMagic實現(xiàn)網(wǎng)站爬蟲的實例代碼
這篇文章主要介紹了springBoot+webMagic實現(xiàn)網(wǎng)站爬蟲的實例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05springboot + JPA 配置雙數(shù)據(jù)源實戰(zhàn)
這篇文章主要介紹了springboot + JPA 配置雙數(shù)據(jù)源實戰(zhàn),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09詳解Idea SpringBoot搭建SpringCloud的準備工作(推薦)
這篇文章主要介紹了Idea SpringBoot搭建SpringCloud的準備工作(推薦),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10SpringBoot2.0整合Redis自定義注入bean組件配置的實戰(zhàn)教程
這篇文章主要介紹了SpringBoot2.0整合Redis自定義注入bean組件配置,我們將基于SpringBoot2.0整合搭建的微服務(wù)項目為奠基,開啟中間件Redis的實戰(zhàn)之路,需要的朋友可以參考下2023-06-06