《java 8 實戰》讀書筆記 -第九章 默認方法

若是在現存的接口上引入了很是多的新方法,全部的實現類都必須進行改造,實現新方法,爲了解決這個問題,Java 8爲了解決這一問題引入了一種新的機制。Java 8中的接口如今支持在聲明方法的同時提供實現,這聽起來讓人驚訝!經過兩種方式能夠完成這種操做。其一,Java 8容許在接口內聲明靜態方法。其二,Java 8引入了一個新功能,叫默認方法,經過默認方法你能夠指定接口方法的默認實現。函數

靜態方法能夠存在於接口內部

1、不斷演進的API

默認方法試它讓類庫的設計者放心地改進應用程序接口,無需擔心對遺留代碼的影響,這是由於實現更新接口的類如今會自動繼承一個默認的方法實現。spa

不一樣類型的兼容性:二進制、源代碼和函數行爲
變動對Java程序的影響大致能夠分紅三種類型的兼容性,分別是:二進制級的兼容、源代碼級的兼容,以及函數行爲的兼容。設計

  • 向接口添加新方法是二進制級的兼容,但最終編譯實現接口的類時卻會發生編譯錯誤。二進制級的兼容性表示現有的二進制執行文件能無縫持續連接(包括驗證、準備和解析)和運行。好比,爲接口添加一個方法就是二進制級的兼容,這種方式下,若是新添加的方法不被調用,接口已經實現的方法能夠繼續運行,不會出現錯誤。
  • 簡單地說,源代碼級的兼容性表示引入變化以後,現有的程序依然能成功編譯經過。
  • 最後,函數行爲的兼容性表示變動發生以後,程序接受一樣的輸入能獲得一樣的結果。好比,爲接口添加新的方法就是函數行爲兼容的,由於新添加的方法在程序中並未被調用(抑或該接口在實現中被覆蓋了)。

2、概述默認方法

默認方法由default修飾符修飾,並像類中聲明的其餘方法同樣包含方法體。好比,你能夠像下面這樣在集合庫中定義一個名爲Sized的接口,在其中定義一個抽象方法size,以及一個默認方法isEmpty:代理

public interface Sized { 
 int size(); 
 default boolean isEmpty() { 
 return size() == 0; 
 } 
}

第3章介紹的不少函數式接口,好比Predicate、Function以及Comparator也引入了新的默認方法,好比Predicate.and或者Function.andThen(記住,函數式接口只包含一個抽象方法,默認方法是種非抽象方法)。code

個抽象類能夠經過實例變量(字段)保存一個通用狀態,而接口是不能有實例變量的。

3、默認方法的使用模式

1.可選方法

你極可能也碰到過這種狀況,類實現了接口,不過卻刻意地將一些方法的實現留白。咱們以Iterator接口爲例來講。Iterator接口定義了hasNext、next,還定義了remove方法。Java 8以前,因爲用戶一般不會使用該方法,remove方法常被忽略。所以,實現Interator接口的類一般會爲remove方法放置一個空的實現,這些都是些毫無用處的模板代碼。繼承

採用默認方法以後,你能夠爲這種類型的方法提供一個默認的實現,這樣實體類就無需在本身的實現中顯式地提供一個空方法。好比,在Java 8中,Iterator接口就爲remove方法提供了一個默認實現,以下所示:接口

interface Iterator<T> { 
 boolean hasNext(); 
 T next(); 
 default void remove() { 
 throw new UnsupportedOperationException(); 
 }

2.行爲的多繼承

  1. 類型的多繼承
  2. 利用正交方法的精簡接口
  3. 組合接口
繼承不該該成爲你一談到代碼複用就試圖倚靠的萬精油。好比,從一個擁有100個方法及字段的類進行繼承就不是個好主意,由於這其實會引入沒必要要的複雜性。你徹底可使用代理有效地規避這種窘境,即建立一個方法經過該類的成員變量直接調用該類的方法。

4、解決衝突的規則

1.解決問題的三條規則

若是一個類使用相同的函數簽名從多個地方(好比另外一個類或接口)繼承了方法,經過三條規則能夠進行判斷。圖片

  • (1)類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。
  • (2)若是沒法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時,優先選擇擁有最具體實現的默認方法的接口,即若是B繼承了A,那麼B就比A更加具體。
  • (3)最後,若是仍是沒法判斷,繼承了多個接口的類必須經過顯式覆蓋和調用指望的方法,顯式地選擇使用哪個默認方法的實現。

2.衝突及如何顯式地消除歧義

對於上面提到的第三種狀況,解決這種兩個可能的有效方法之間的衝突,沒有太多方案;你只能顯式地決定你但願在C中使用哪個方法。爲了達到這個目的,你能夠覆蓋類C中的hello方法,在它的方法體內顯式地調用你但願調用的方法。Java 8中引入了一種新的語法X.super.m(…),其中X是你但願調用的m方法所在的父接口。舉例來講,若是你但願C使用來自於B的默認方法,它的調用方式看起來就以下所示:rem

public class C implements B, A { 
 void hello(){ 
 B.super.hello(); 
 } 
}

3.菱形繼承問題

讓咱們考慮最後一種場景,它亦是C++裏中最使人頭痛的難題。編譯器

public interface A{ 
 default void hello(){ 
 System.out.println("Hello from A"); 
 } 
} 

public interface B extends A { } 

public interface C extends A { } 

public class D implements B, C { 
 public static void main(String... args) { 
 new D().hello(); 
 } 
}

這種問題叫「菱形問題」,由於類的繼承關係圖形狀像菱形。這種狀況下類D中的默認方法到底繼承自什麼地方 ——源自B的默認方法,仍是源自C的默認方法?實際上只有一個方法聲明能夠選擇。只有A聲明瞭一個默認方法。因爲這個接口是D的父接口,代碼會打印輸出「Hello from A」。
圖片描述
如今,咱們看看另外一種狀況,若是B中也提供了一個默認的hello方法,而且函數簽名跟A中的方法也徹底一致,這時會發生什麼狀況呢?根據規則(2),編譯器會選擇提供了更具體實現的接口中的方法。因爲B比A更加具體,因此編譯器會選擇B中聲明的默認方法。若是B和C都使用相同的函數簽名聲明瞭hello方法,就會出現衝突,正如咱們以前所介紹的,你須要顯式地指定使用哪一個方法。順便提一句,若是你在C接口中添加一個抽象的hello方法(此次添加的不是一個默認方法),會發生什麼狀況呢?你可能也想知道答案。

public interface C extends A { 
 void hello(); 
}

這個新添加到C接口中的抽象方法hello比由接口A繼承而來的hello方法擁有更高的優先級,由於C接口更加具體。所以,類D如今須要爲hello顯式地添加實現,不然該程序沒法經過編譯

相關文章
相關標籤/搜索