許多開發語言都將函數表達式集成到了其集合庫中。這樣比循環方式所需的代碼更少,而且更加容易理解。如下面的循環爲例:java
for(int i = 0; i < list.size(); i++) System.out.println(list.get(i));
事實上有一種更好的方式。API開發人員能夠提供一個forEach方法,用來將一個函數應用到集合的每一個元素上。下面是使用這種方式編寫的一個簡單調用:安全
package java8test; import java.util.Arrays; import java.util.List; public class T3 { public static void main(String[] args) { List<String> list = Arrays.asList("a","b","c","d","e","f"); //注意這一句代碼 list.forEach(System.out::println); } }
若是集合庫是徹底從新設計的,這樣作不會有什麼問題。可是,Java的集合庫是許多年之前設計的,這就會帶來一個問題。若是Collection接口添加了新的方法,例如forEach,那麼每一個實現了Collection接口的自定義類就必須都實現該方法。這在java中是徹底沒法接受的。ide
java設計者們但願經過容許接口包含帶有具體實現的方法(稱爲默認方法)來一勞永逸地解決這個問題。這些方法能夠被安全地添加到已有的接口中。這裏咱們將詳細講解默認方法。注:Java8中,forEach方法已經添加到Iterable接口中(它是Collection接口的父接口)。假設有以下接口:函數
interface Person { long getId(); //注意這裏有一個default關鍵字 default String getName(){ return "John Q. Public"; } }
該接口有兩個方法:一個抽象方法getId,以及一個默認方法getName。固然,實現Person接口的具體類必須實現getId方法,可是它能夠選擇保留getName的實現,或者重寫它。spa
默認方法終結了之前的一種經典模式。即提供一個接口,以及一個實現接口的大多數或所有方法的抽象類,例如:Collection/AbstractCollection或WindowListener/WindowAdapter。如今你只須要在接口中實現那些方法。scala
若是一個接口中定義了一個默認方法,而另一個父類或接口中又定義了一個同名的方法,該選哪一個呢?像scala和C++等語言可能會有一套複雜的規則來解決這種二義性,可是幸運的是,Java中的規則要簡單得多,以下所示:設計
選擇父類中的方法。若是一個父類提供了具體的實現方法,那麼接口中具備相同名稱和參數的默認方法會被忽略。code
接口衝突。若是一個父接口提供了一個默認方法,而另外一個接口也提供了一個具備相同名稱和參數類型的方法(無論該方法是不是默認方法),那麼你必須經過覆蓋該方法來解決衝突。 繼承
咱們來詳細理解一下第二條規則。假定另外一個接口也含有一個名爲getName的方法:接口
interface Named{ default String getName(){ return getClass().getName() + "_" + hashCode(); } }
若是你編寫了一個同時實現這兩個接口的類,會發生什麼事呢?
class Student implements Person,Named { ...... }
該類會繼承由Person和Named接口同時提供的getName方法,可是這兩個方法的實現並不一致。Java編譯器會報告一個錯誤,並交由開發人員來解決這種衝突,而不會自動選擇其中一個。對於這種狀況,你只須要在Student類中提供一個getName方法,在該方法中再選擇調用其中一個接口中的方法,以下所示:
interface Person{ long getId(); default String getName(){ return "John Q. Public"; } } interface Named{ default String getName(){ return getClass().getName() + "_" + hashCode(); } } class Student implements Person,Named{ @Override public long getId() { return 0; } public String getName(){ //注意這一句:Person.super.getName() return Person.super.getName(); } }
如今咱們假定Named接口沒有提供getName方法的一個默認實現:
interface Named{ String getname(); }
若是這樣,Student類能繼承Person接口中的默認方法嗎?也許這樣說得過去,可是Java設計者們爲了保持統一,仍是選擇了與以前同樣的處理方式。兩個接口如何衝突不重要,只要有一個接口提供了實現,編譯器就會報告一個錯誤,而開發人員必須手動解決這種衝突。注:固然,若是兩個接口都沒有爲共享方法提供一個默認實現,那麼咱們就又回到了Java8以前的狀況,也就不存在什麼衝突了。
如今咱們考慮這樣一個類,它繼承了父類並實現了某個接口,而這個父類和接口中都有一個同名的方法。例如,假設Person是一個類,而Student類的定義以下所示:
class Student extends Person implements Named {......}
在這種狀況下,只有父類中的方法會起做用,接口中的任何默認方法都會被忽略。在這個例子中,無論Named接口中的getName方法是不是默認方法,Student都會繼承Person類中的getName方法。這就是「類優先」的規則。「類優先」的規則能夠保證Java7的兼容性。若是你在接口中添加了一個默認方法,它對Java8之前編寫的代碼不會產生任何影響。示例:
package java8test; public class T4 { public static void main(String[] args) { Student stu = new Student(); //類優先,會打印出:John Q. Public System.out.println(stu.getName()); } } class Person{ public long getId(){ return 100L; }; public String getName(){ return "John Q. Public"; } } interface Named{ default String getName(){ return getClass().getName() + "_" + hashCode(); } } class Student extends Person implements Named{}