默認方法

1.簡述

在Java8之前隧期,Java程序接口是將相關方法按照約定組合到一起的方式按声。實現(xiàn)接口的類必須為接口中定義的每個方法提供一個實現(xiàn)燃少,或者從父類中繼承它的實現(xiàn)邑贴。但是席里,一旦類庫的設計者需要更新接口,向其中加入新的方法拢驾,這種方式就會出現(xiàn)問題〗贝牛現(xiàn)實情況是,現(xiàn)存的實體類往往不在接口設計者的控制范圍之內繁疤,這些實體類為了適配新的接口約定也需要進行修改咖为。由于Java8的API在現(xiàn)存的接口上引入了非常多的新方法,這種變化帶來的問題也愈加嚴重稠腊。

在Java8中為了解決這個問題引入了一種新的機制躁染。Java8中的接口現(xiàn)在支持在聲明方法的同時提供實現(xiàn)。有兩種方式可以完成這種操作架忌。其一吞彤,Java8允許在接口內聲明靜態(tài)方法。其二鳖昌,Java8引入了一個新功能备畦,叫默認方法。通過默認方法许昨,即使實現(xiàn)接口的方法也可以自動繼承默認的實現(xiàn)懂盐,你可以讓你的接口可以平滑地進行接口的進化和演進。比如我們的List接口中的sort方法是java8中全新的方法,定義如下:

default void sort(Comparator<? super E> c){
    Collections.sort(this, c);
}

在方法有個default修飾符用來表示這是默認方法糕档。

2.進化的API

為了理解為什么一旦API發(fā)布之后莉恼,它的演進就變得非常困難拌喉,我們假設你是一個流行Java繪圖庫的設計者(為了說明本節(jié)的內容,我們做了這樣的假想)俐银。你的庫中包含了一個Resizable接口尿背,它定義了一個簡單的可縮放形狀必須支持的很多方法,比如:setHeight捶惜、 setWidth田藐、getHeight、getWidth以及setAbsoluteSize吱七。此外汽久,你還提供了幾個額外的實現(xiàn)(out-of-boximplementation),如正方形踊餐、長方形景醇。由于你的庫非常流行,你的一些用戶使用Resizable接口創(chuàng)建了他們自己感興趣的實現(xiàn)吝岭,比如橢圓三痰。

發(fā)布API幾個月之后,你突然意識到Resizable接口遺漏了一些功能窜管。比如散劫,如果接口提供一個setRelativeSize方法,可以接受參數(shù)實現(xiàn)對形狀的大小進行調整微峰,那么接口的易用性會更好舷丹。你會說這看起來很容易啊:為Resizable接口添加setRelativeSize方法蜓肆,再更新Square和Rectangle的實現(xiàn)就好了颜凯。不過,事情并非如此簡單仗扬!你要考慮已經使用了你接口的用戶症概,他們已經按照自身的需求實現(xiàn)了Resizable接口,他們該如何應對這樣的變更呢早芭?非常不幸彼城,你無法訪問,也無法改動他們實現(xiàn)了Resizable接口的類退个。這也是Java庫的設計者需要改進JavaAPI時面對的問題募壕。讓我們以一個具體的實例為例,深入探討修改一個已發(fā)布接口的種種后果语盈。

2.1初始化版本的API

Resizable最開始的版本如下:

public interface Resizable{
    int getWidth();
    int getHeight();
    void setWidth(int width);
    void setHeight(int height);
    void setAbsoluteSize(int width, int height);
}

這時候有一位用戶實現(xiàn)了你的Resizable接口舱馅,創(chuàng)建了Ellipse類:

public class Ellipse implements Resizable {
    @Override
    public int getWidth() {
        return 0;
    }

    @Override
    public int getHeight() {
        return 0;
    }

    @Override
    public void setWidth(int width) {

    }

    @Override
    public void setHeight(int height) {

    }

    @Override
    public void setAbsoluteSize(int width, int height) {

    }
}

2.2第二版本API

庫上線使用幾個月之后,你收到很多請求刀荒,要求你更新Resizable的實現(xiàn)代嗤,所以你更新了一個方法棘钞。

public interface Resizable{
    int getWidth();
    int getHeight();
    void setWidth(int width);
    void setHeight(int height);
    void setAbsoluteSize(int width, int height);
    void setRelativeSize(int wFactor, int hFactor);//第二版本API
}

接下來用戶便會面臨很多問題。首先干毅,接口現(xiàn)在要求它所有的實現(xiàn)類添加setRelativeSize方法的實現(xiàn)宜猜。但我們剛才的用戶最初實現(xiàn)的Ellipse類并未包含setRelativeSize方法。向接口添加新方法是二進制兼容的硝逢,這意味著如果不重新編譯該類姨拥,即使不實現(xiàn)新的方法,現(xiàn)有類的實現(xiàn)依舊可以運行趴捅。但是這種情況少之又少垫毙,基本項目每次發(fā)布時都會重新編譯,所以必定會報錯拱绑。

最后,更新已發(fā)布API會導致后向兼容性問題丽蝎。這就是為什么對現(xiàn)存API的演進猎拨,比如官方發(fā)布的Java.Collection.API,會給用戶帶來麻煩屠阻。當然红省,還有其他方式能夠實現(xiàn)對API的改進,但是都不是明智的選擇国觉。比如吧恃,你可以為你的API創(chuàng)建不同的發(fā)布版本,同時維護老版本和新版本麻诀,但這是非常費時費力的痕寓,原因如下。其一蝇闭,這增加了你作為類庫的設計者維護類庫的復雜度呻率。其次,類庫的用戶不得不同時使用一套代碼的兩個版本呻引,而這會增大內存的消耗礼仗,延長程序的載入時間,因為這種方式下項目使用的類文件數(shù)量更多了逻悠。

這就是我們默認方法所要做的工作元践。它讓我們的類庫設計者放心地改進應用程序接口,無需擔憂對遺留代碼的影響童谒。

3.詳解默認方法

經過前述的介紹单旁,我們已經了解了向已發(fā)布的API添加方法,會對我們現(xiàn)存的代碼會造成多大的危害惠啄。默認方法是Java8中引入的一個新特性慎恒,依靠他我們可以在實現(xiàn)類中不用提供實現(xiàn)任内。
我們要使用我們的默認方法非常簡單,只需要在我們要實現(xiàn)的方法簽名前面添加default修飾符進行修飾融柬,并像類中聲明的其他方法一樣包含方法體死嗦。如下面的接口一樣:

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

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

3.1默認方法的使用模式

3.1.1可選方法

你有時候會碰到這種情況粒氧,類實現(xiàn)了接口越除,不過卻可以將一些方法的實現(xiàn)留白。比如我們Iterator接口外盯,我們一般不會去實現(xiàn)remove方法摘盆,經常實現(xiàn)都會留白,在Java8中為了解決這種辦法回味我們的remove方法添加默認的實現(xiàn),如下:

public interface Iterator<E> {
    
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

通過這種方式饱苟,我們可以減少無效的模板代碼孩擂。實現(xiàn)Iterator接口的每一個類都不需要再次實現(xiàn)remove的模板方法了。

3.1.2多繼承

默認方法讓之前的Java是不支持多繼承箱熬,但是默認方法的出現(xiàn)讓多繼承在java中變得可能了类垦。

Java的類只能繼承單一的類,但是一個類可以實現(xiàn)多接口城须。要確認也很簡單蚤认,下面是Java API中對ArrayList類的定義:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable,
Serializable, Iterable<E>, Collection<E> {
}

3.1.3沖突問題

我們知道Java語言中一個類只能繼承一個父類,但是一個類可以實現(xiàn)多個接口糕伐。隨著默認方法在Java8中引入砰琢,有可能出現(xiàn)一個類繼承了多個方法而它們使用的卻是同樣的函數(shù)簽名。這種情況下良瞧,類會選擇使用哪一個函數(shù)陪汽?在實際情況中,雖然這樣的沖突很難發(fā)生莺褒,但是一旦發(fā)生掩缓,就必須要規(guī)定一套約定來處理這些沖突。這一節(jié)中遵岩,我們會介紹Java編譯器如何解決這種潛在的沖突你辣。

public interface A {
    default void hello(){
        System.out.println("i am A");
    }
}
interface B extends A{
    default void hello(){
        System.out.println("i am B");
    }
}
class C implements A,B{
    public static void main(String[] args) {
        new C().hello();
    }
}

上面的代碼會輸出i am B。為什么呢尘执?我們下面有三個規(guī)則:

  1. 類中的方法優(yōu)先級最高舍哄。類或父類中的聲明的方法的優(yōu)先級高于任何聲明為默認方法的優(yōu)先級。
  2. 如果無法依據(jù)第一條進行判斷誊锭,那么子接口的優(yōu)先級更高:函數(shù)簽名相同時表悬,優(yōu)先選擇擁有最具體實現(xiàn)的默認方法的接口。如果B繼承了A丧靡,那么B就比A的更具體蟆沫。
  3. 最后籽暇,如果還是無法判斷,繼承了多個接口的類必須通過顯示覆蓋和調用期望的方法饭庞,顯式地選擇使用哪一個默認方法的實現(xiàn)戒悠。

接下來舉幾個例子

public interface A {
    default void hello(){
        System.out.println("i am A");
    }
}
interface B extends A{
    default void hello(){
        System.out.println("i am B");
    }
}
class D implements A{
    public void hello(){
        System.out.println("i am D");
    }
}
class C extends D implements A,B{
    public static void main(String[] args) {
        new C().hello();
    }
}

上面會輸出D,遵循我們的第一條原則舟山,類中的方法優(yōu)先級最高绸狐。

public interface A {
    default void hello(){
        System.out.println("i am A");
    }
}
interface B {
    default void hello(){
        System.out.println("i am B");
    }
}

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

上面代碼會出現(xiàn)編譯錯誤:Error:(19, 1) java: 類 java8.C從類型 java8.A 和 java8.B 中繼承了hello() 的不相關默認值,這個時候必須利用第三條,顯式得去調用父類的接口:

class C implements A,B{
    public void hello(){
        B.super.hello();
    }
    public static void main(String[] args) {
        new C().hello();
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末累盗,一起剝皮案震驚了整個濱河市寒矿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌若债,老刑警劉巖符相,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蠢琳,居然都是意外死亡主巍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門挪凑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逛艰,你說我怎么就攤上這事躏碳。” “怎么了散怖?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵菇绵,是天一觀的道長。 經常有香客問我镇眷,道長咬最,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任欠动,我火速辦了婚禮永乌,結果婚禮上,老公的妹妹穿的比我還像新娘具伍。我一直安慰自己翅雏,他們只是感情好,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布人芽。 她就那樣靜靜地躺著望几,像睡著了一般。 火紅的嫁衣襯著肌膚如雪萤厅。 梳的紋絲不亂的頭發(fā)上橄抹,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天靴迫,我揣著相機與錄音,去河邊找鬼楼誓。 笑死玉锌,一個胖子當著我的面吹牛,可吹牛的內容都是我干的慌随。 我是一名探鬼主播芬沉,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼阁猜!你這毒婦竟也來了丸逸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤剃袍,失蹤者是張志新(化名)和其女友劉穎黄刚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體民效,經...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡憔维,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了畏邢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片业扒。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舒萎,靈堂內的尸體忽然破棺而出程储,到底是詐尸還是另有隱情,我是刑警寧澤臂寝,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布章鲤,位于F島的核電站,受9級特大地震影響咆贬,放射性物質發(fā)生泄漏败徊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一掏缎、第九天 我趴在偏房一處隱蔽的房頂上張望皱蹦。 院中可真熱鬧,春花似錦御毅、人聲如沸根欧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凤粗。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫌拣,已是汗流浹背柔袁。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留异逐,地道東北人捶索。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像灰瞻,于是被迫代替她去往敵國和親腥例。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內容