Dating Java8系列之default默認方法

翎野君/文

 

圖片

 

圖片

引言

 

傳統上,Java程序的接口是將相關方法按照約定組合到一起。實現接口的類必須爲接口中定義的每個方法提供一個實現,或者從父類中繼承它的實現。

 

不斷迭代的API

默認方法的引入就是爲了,以兼容的方式,解決像 Java API這樣的類庫,演進迭代問題。

理解演進迭代

爲了理解爲什麼一旦API發佈之後,它的演進就變得非常困難,我們假設你是一個Github上的開源作者,興致勃勃的寫了一個開源項目,然後放到了Github上面。

沒過多久你的項目就被其他用戶Fork到本地,然後開始使用了起來,並且在項目中對你發佈的一些接口進行了實現。

發佈API幾個月之後,你突然意識到接口中遺漏了一些功能。需要調整原來的接口,在其中新增方法,這樣的話接口的易用性會更好。

不過,事情並非如此簡單!你要考慮已經使用了你接口的用戶,他們可能已經按照自身的需求實現了你的接口,倘若你更新了接口的API並重新進行了發佈,那麼所以實現了你的接口的地方,都需要進行改動。

圖片

簡而言之,向接口添加方法是諸多問題的罪惡之源;一旦接口發生變化,實現這些接口的類往往也需要更新,提供新添方法的實現才能適配接口的變化。如果你對接口以及它所有相關的實現有完全的控制,這可能不是個大問題。但是這種情況是極少的。

這就是引入默認方法的目的:它讓類可以自動地繼承接口的一個默認實現。

 

圖片

概述

 

1.默認方法

默認方法是Java 8中引入的一個新特性,希望能借此以兼容的方式改進API。現在,接口包含的方法簽名在它的實現類中也可以不提供實現。那麼,誰來具體實現這些方法呢?實際上,缺失的方法實現會作爲接口的一部分由實現類繼承(所以命名爲默認實現),而無需由實現類提供。

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

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

這樣任何一個實現了Sized接口的類都會自動繼承isEmpty的實現。

2.使用默認方法

可選方法

你肯定碰到過這種情況,一個類實現了接口,不過卻將一些實現方法進行留白,沒有實現。

我們以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();        }}

通過這種方式,你可以減少無效的模板代碼。實現Iterator接口的每一個類都不需要再聲明一個空的remove方法了,因爲它現在已經有一個默認的實現。

行爲的多繼承

Java的類只能繼承單一的類,但是一個類可以實現多接口。

下面是Java API中對ArrayList類的定義:

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {        }

這個例子中ArrayList繼承了一個類,實現了四個接口。因此ArrayList實際是 五個類型的直接子類,分別是:AbstractList,List,RandomAccess,Cloneable,Serializable。所以,在某種程度上,我們就有了類型的多繼承。

 

由於Java 8中接口方法可以包含實現,類可以從多個接口中繼承它們的行爲(即實現的代碼)。 讓我們從一個例子入手,看看如何充分利用這種能力來爲我們服務。

public interface MoveService {
void run();
default void flash() { System.out.println("閃現!!!"); }}public interface SkillService {
void q();
void w();
void e();
default void r() { System.out.println("默認大招:傷害100點"); }}public class Shooter implements MoveService, SkillService { @Override public void run() { System.out.println("寒冰射手 走~~"); } @Override public void q() { System.out.println("寒冰 q"); } @Override public void w() { System.out.println("寒冰 w"); } @Override public void e() { System.out.println("寒冰 e"); } public void r(){ System.out.println("寒冰 傷害100點!!同時冰凍對方5s!!!"); } public static void main(String[] args) { new Shooter().r(); }}

 

圖片

方法衝突

 

我們知道Java語言中一個類只能繼承一個類,但是一個類可以實現多個接口。
隨着默認方法在Java 8中引入,有可能出現一個類繼承了多個方法而它們使用的卻是同樣的函數簽名。這種情況下,在一個類中使用父類的默認方法,這樣會有衝突嗎,沒有的話,那會選擇哪一個呢?

1.解決衝突的三條規則

如果一個類使用相同的函數簽名從多個地方(比如另一個類或接口)繼承了方法,通過三條規則可以進行判斷。

  • 類中的方法優先級最高。類或父類中聲明的方法的優先級高於任何聲明爲默認方法的優先級。

  • 如果無法依據第一條進行判斷,那麼子接口的優先級更高:函數簽名相同時,優先選擇擁有最具體實現的默認方法的接口,即如果B繼承了A,那麼B就比A更加具體。

  • 最後,如果還是無法判斷,繼承了多個接口的類必須通過顯式覆蓋和調用期望的方法,顯式地選擇使用哪一個默認方法的實現。

2.衝突示例

類中的方法優先級最高

public interface PlayerService {    default void stop() {        System.out.println("播放器--停止!!!");    }}
public class SonyPlayerServiceImpl implements PlayerService { public void stop() { System.out.println("Sony播放器--停止!!!"); } public static void main(String[] args) { new SonyPlayerServiceImpl().stop(); }}

選擇提供了最具體實現的默認方法的接口

public interface PlayerService {    default void showLyric() {        System.out.println("PlayerService : show lyric");    }}public interface RecordService extends PlayerService{    default void showLyric() {        System.out.println("RecordService : show lyric");    }}public class Question1 implements RecordService {    public static void main(String[] args) {        new Question1().showLyric();        System.out.println("應該選擇的是提供了最具體實現的默認方法的接口。由於RecordService比PlayerService更具體,所以應該選擇由於RecordService比PlayerService更具體的showLyric方法。");    }}

衝突和顯示的消除歧義

public class Question2 implements PlayerService,RecordService {    @Override    public void showLyric() {        PlayerService.super.showLyric();        RecordService.super.showLyric();    }
public static void main(String[] args) { new Question2().showLyric(); }}

 

圖片

小結

 

  • Java 8中的接口可以通過默認方法提供方法的代碼實現。

  • 默認方法的開頭以關鍵字default修飾,方法體與常規的類方法相同。

  • 默認方法的出現能幫助庫的設計者以後向兼容的方式演進API。

  • 默認方法可以用於創建可選方法和行爲的多繼承。

  • 我們有辦法解決由於一個類從多個接口中繼承了擁有相同函數簽名的方法而導致的衝突。

  • 類或者父類中聲明的方法的優先級高於任何默認方法。如果前一條無法解決衝突,那就選擇同函數簽名的方法中實現得最具體的那個接口的方法。

  • 兩個默認方法都同樣具體時,你需要在類中覆蓋該方法,顯式地選擇使用哪個接口中提供的默認方法。

 

作者:翎野君
博客:https://www.cnblogs.com/lingyejun/

 

本篇文章如有幫助到您,請給「翎野君」點個贊,感謝您的支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章