成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

《Java8實(shí)戰(zhàn)》-第九章筆記(默認(rèn)方法)

sanyang / 1324人閱讀

摘要:默認(rèn)方法傳統(tǒng)上,程序的接口是將相關(guān)方法按照約定組合到一起的方式。其一,允許在接口內(nèi)聲明靜態(tài)方法。實(shí)際上,到目前為止你已經(jīng)使用了多個(gè)默認(rèn)方法。通過(guò)它,我們能夠知道一個(gè)方法是否為默認(rèn)方法。這就是默認(rèn)方法試圖解決的問(wèn)題。

默認(rèn)方法

傳統(tǒng)上,Java程序的接口是將相關(guān)方法按照約定組合到一起的方式。實(shí)現(xiàn)接口的類必須為接口中定義的每個(gè)方法提供一個(gè)實(shí)現(xiàn),或者從父類中繼承它的實(shí)現(xiàn)。但是,一旦類庫(kù)的設(shè)計(jì)者需要更新接口,向其中加入新的方法,這種方式就會(huì)出現(xiàn)問(wèn)題?,F(xiàn)實(shí)情況是,現(xiàn)存的實(shí)體類往往不在接口設(shè)計(jì)者的控制范圍之內(nèi),這些實(shí)體類為了適配新的接口約定也需要進(jìn)行修改。由于Java 8的API在現(xiàn)存的接口上引入了非常多的新方法,這種變化帶來(lái)的問(wèn)題也愈加嚴(yán)重,一個(gè)例子就是前幾章中使用過(guò)的 List 接口上的 sort 方法。想象一下其他備選集合框架的維護(hù)人員會(huì)多么抓狂吧,像Guava和Apache Commons這樣的框架現(xiàn)在都需要修改實(shí)現(xiàn)了 List 接口的所有類,為其添加sort 方法的實(shí)現(xiàn)。

且慢,其實(shí)你不必驚慌。Java 8為了解決這一問(wèn)題引入了一種新的機(jī)制。Java 8中的接口現(xiàn)在支持在聲明方法的同時(shí)提供實(shí)現(xiàn),這聽(tīng)起來(lái)讓人驚訝!通過(guò)兩種方式可以完成這種操作。其一,Java 8允許在接口內(nèi)聲明靜態(tài)方法。其二,Java 8引入了一個(gè)新功能,叫默認(rèn)方法,通過(guò)默認(rèn)方法你可以指定接口方法的默認(rèn)實(shí)現(xiàn)。換句話說(shuō),接口能提供方法的具體實(shí)現(xiàn)。因此,實(shí)現(xiàn)接口的類如果不顯式地提供該方法的具體實(shí)現(xiàn),就會(huì)自動(dòng)繼承默認(rèn)的實(shí)現(xiàn)。這種機(jī)制可以使你平滑地進(jìn)行接口的優(yōu)化和演進(jìn)。實(shí)際上,到目前為止你已經(jīng)使用了多個(gè)默認(rèn)方法。兩個(gè)例子就是你前面已經(jīng)見(jiàn)過(guò)的 List 接口中的 sort ,以及 Collection 接口中的 stream 。

第1章中我們看到的 List 接口中的 sort 方法是Java 8中全新的方法,它的定義如下:

default void sort(Comparator c){
    Collections.sort(this, c);
}

請(qǐng)注意返回類型之前的新 default 修飾符。通過(guò)它,我們能夠知道一個(gè)方法是否為默認(rèn)方法。這里 sort 方法調(diào)用了 Collections.sort 方法進(jìn)行排序操作。由于有了這個(gè)新的方法,我們現(xiàn)在可以直接通過(guò)調(diào)用 sort ,對(duì)列表中的元素進(jìn)行排序。

List numbers = Arrays.asList(3, 5, 1, 2, 6);
numbers.sort(Comparator.naturalOrder());

不過(guò)除此之外, 這段代碼中還有些其他的新東西。注意到了嗎,我們調(diào)用了Comparator.naturalOrder 方法。這是 Comparator 接口的一個(gè)全新的靜態(tài)方法,它返回一個(gè)Comparator 對(duì)象,并按自然序列對(duì)其中的元素進(jìn)行排序(即標(biāo)準(zhǔn)的字母數(shù)字方式排序)。

第4章中你看到的 Collection 中的 stream 方法的定義如下:

default Stream stream() {
    return StreamSupport.stream(spliterator(), false);
}

我們?cè)谥暗膸渍轮写罅渴褂昧嗽摲椒▉?lái)處理集合,這里 stream 方法中調(diào)用了SteamSupport.stream 方法來(lái)返回一個(gè)流。你注意到 stream 方法的主體是如何調(diào)用 spliterator 方法的了嗎?它也是 Collection 接口的一個(gè)默認(rèn)方法。

喔噢!這些接口現(xiàn)在看起來(lái)像抽象類了吧?是,也不是。它們有一些本質(zhì)的區(qū)別,我們?cè)谶@一章中會(huì)針對(duì)性地進(jìn)行討論。但更重要的是,你為什么要在乎默認(rèn)方法?默認(rèn)方法的主要目標(biāo)用戶是類庫(kù)的設(shè)計(jì)者啊。

簡(jiǎn)而言之,向接口添加方法是諸多問(wèn)題的罪惡之源;一旦接口發(fā)生變化,實(shí)現(xiàn)這些接口的類往往也需要更新,提供新添方法的實(shí)現(xiàn)才能適配接口的變化。如果你對(duì)接口以及它所有相關(guān)的實(shí)現(xiàn)有完全的控制,這可能不是個(gè)大問(wèn)題。但是這種情況是極少的。這就是引入默認(rèn)方法的目的:它讓類可以自動(dòng)地繼承接口的一個(gè)默認(rèn)實(shí)現(xiàn)。

因此,如果你是個(gè)類庫(kù)的設(shè)計(jì)者,這一章的內(nèi)容對(duì)你而言會(huì)十分重要,因?yàn)槟J(rèn)方法為接口的演進(jìn)提供了一種平滑的方式,你的改動(dòng)將不會(huì)導(dǎo)致已有代碼的修改。此外,正如我們后文會(huì)介紹的,默認(rèn)方法為方法的多繼承提供了一種更靈活的機(jī)制,可以幫助你更好地規(guī)劃你的代碼結(jié)構(gòu):類可以從多個(gè)接口繼承默認(rèn)方法。因此,即使你并非類庫(kù)的設(shè)計(jì)者,也能在其中發(fā)現(xiàn)感興趣的東西。

章的結(jié)構(gòu)如下。首先,我們會(huì)跟你一起剖析一個(gè)API演化的用例,探討由此引發(fā)的各種問(wèn)題。緊接著我們會(huì)解釋什么是默認(rèn)方法,以及它們?cè)谶@個(gè)用例中如何解決相應(yīng)的問(wèn)題。之后,我們會(huì)展示如何創(chuàng)建自己的默認(rèn)方法,構(gòu)造Java語(yǔ)言中的多繼承。最后,我們會(huì)討論一個(gè)類在使用一個(gè)簽名同時(shí)繼承多個(gè)默認(rèn)方法時(shí),Java編譯器是如何解決可能的二義性(模糊性)問(wèn)題的。

不斷演進(jìn)的 API

為了理解為什么一旦API發(fā)布之后,它的演進(jìn)就變得非常困難,我們假設(shè)你是一個(gè)流行Java繪圖庫(kù)的設(shè)計(jì)者(為了說(shuō)明本節(jié)的內(nèi)容,我們做了這樣的假想)。你的庫(kù)中包含了一個(gè) Resizable接口,它定義了一個(gè)簡(jiǎn)單的可縮放形狀必須支持的很多方法, 比如: setHeight 、 setWidth 、getHeight 、 getWidth 以及 setAbsoluteSize 。此外,你還提供了幾個(gè)額外的實(shí)現(xiàn)(out-of-boximplementation),如正方形、長(zhǎng)方形。由于你的庫(kù)非常流行,你的一些用戶使用 Resizable 接口創(chuàng)建了他們自己感興趣的實(shí)現(xiàn),比如橢圓。

發(fā)布API幾個(gè)月之后,你突然意識(shí)到 Resizable 接口遺漏了一些功能。比如,如果接口提供一個(gè) setRelativeSize 方法,可以接受參數(shù)實(shí)現(xiàn)對(duì)形狀的大小進(jìn)行調(diào)整,那么接口的易用性會(huì)更好。你會(huì)說(shuō)這看起來(lái)很容易?。簽?Resizable 接口添加 setRelativeSize 方法,再更新 Square和 Rectangle 的實(shí)現(xiàn)就好了。不過(guò),事情并非如此簡(jiǎn)單!你要考慮已經(jīng)使用了你接口的用戶,他們已經(jīng)按照自身的需求實(shí)現(xiàn)了 Resizable 接口,他們?cè)撊绾螒?yīng)對(duì)這樣的變更呢?非常不幸,你無(wú)法訪問(wèn),也無(wú)法改動(dòng)他們實(shí)現(xiàn)了 Resizable 接口的類。這也是Java庫(kù)的設(shè)計(jì)者需要改進(jìn)Java API時(shí)面對(duì)的問(wèn)題。讓我們以一個(gè)具體的實(shí)例為例,深入探討修改一個(gè)已發(fā)布接口的種種后果。

初始版本的 API

Resizable 接口的最初版本提供了下面這些方法:

public interface Drawable {
    void draw();
}

public interface Resizable extends Drawable {
    int getWidth();

    void setWidth(int width);

    int getHeight();

    void setHeight(int height);

    void setAbsoluteSize(int width, int height);
}

用戶實(shí)現(xiàn)
你的一位鐵桿用戶根據(jù)自身的需求實(shí)現(xiàn)了 Resizable 接口,創(chuàng)建了 Ellipse 類:

public class Ellipse implements Resizable {
    ...
}

他實(shí)現(xiàn)了一個(gè)處理各種 Resizable 形狀(包括 Ellipse )的游戲:

public class Square implements Resizable {
    ...
}
public class Triangle implements Resizable {
    ...
}
public class Game {
    public static void main(String[] args) {
        List resizableShapes =
                Arrays.asList(new Square(), new Triangle(), new Ellipse());
        Utils.paint(resizableShapes);
    }
}
public class Utils {
    public static void paint(List list) {
        list.forEach(r -> {
            r.setAbsoluteSize(42, 42);
            r.draw();
        });
    }
}
第二版 API

庫(kù)上線使用幾個(gè)月之后,你收到很多請(qǐng)求,要求你更新 Resizable 的實(shí)現(xiàn),讓 Square Triangle 以及其他的形狀都能支持 setRelativeSize 方法。為了滿足這些新的需求,你發(fā)布了第二版API。

public interface Resizable extends Drawable {
    int getWidth();

    void setWidth(int width);

    int getHeight();

    void setHeight(int height);

    void setAbsoluteSize(int width, int height);

    void setRelativeSize(int wFactor, int hFactor);
}

用戶面臨的窘境
對(duì) Resizable 接口的更新導(dǎo)致了一系列的問(wèn)題。首先,接口現(xiàn)在要求它所有的實(shí)現(xiàn)類添加setRelativeSize 方法的實(shí)現(xiàn)。但是用戶最初實(shí)現(xiàn)的 Ellipse 類并未包含 setRelativeSize方法。向接口添加新方法是二進(jìn)制兼容的,這意味著如果不重新編譯該類,即使不實(shí)現(xiàn)新的方法,現(xiàn)有類的實(shí)現(xiàn)依舊可以運(yùn)行。不過(guò),用戶可能修改他的游戲,在他的 Utils.paint 方法中調(diào)用setRelativeSize 方法,因?yàn)?paint 方法接受一個(gè) Resizable 對(duì)象列表作為參數(shù)。如果傳遞的是一個(gè) Ellipse 對(duì)象,程序就會(huì)拋出一個(gè)運(yùn)行時(shí)錯(cuò)誤,因?yàn)樗⑽磳?shí)現(xiàn) setRelativeSize 方法:

Exception in thread "main" java.lang.AbstractMethodError:lambdasinaction.chap9.Ellipse.setRelativeSize(II)V

其次,如果用戶試圖重新編譯整個(gè)應(yīng)用(包括 Ellipse 類),他會(huì)遭遇下面的編譯錯(cuò)誤:

Error:(9, 8) java: xin.codeream.java8.chap9.Ellipse不是抽象的, 并且未覆蓋
xin.codeream.java8.chap9.Resizable中的抽象方法setRelativeSize(int,int)

最后,更新已發(fā)布API會(huì)導(dǎo)致后向兼容性問(wèn)題。這就是為什么對(duì)現(xiàn)存API的演進(jìn),比如官方發(fā)布的Java Collection API,會(huì)給用戶帶來(lái)麻煩。當(dāng)然,還有其他方式能夠?qū)崿F(xiàn)對(duì)API的改進(jìn),但是都不是明智的選擇。比如,你可以為你的API創(chuàng)建不同的發(fā)布版本,同時(shí)維護(hù)老版本和新版本,但這是非常費(fèi)時(shí)費(fèi)力的,原因如下。其一,這增加了你作為類庫(kù)的設(shè)計(jì)者維護(hù)類庫(kù)的復(fù)雜度。其次,類庫(kù)的用戶不得不同時(shí)使用一套代碼的兩個(gè)版本,而這會(huì)增大內(nèi)存的消耗,延長(zhǎng)程序的載入時(shí)間,因?yàn)檫@種方式下項(xiàng)目使用的類文件數(shù)量更多了。

這就是默認(rèn)方法試圖解決的問(wèn)題。它讓類庫(kù)的設(shè)計(jì)者放心地改進(jìn)應(yīng)用程序接口,無(wú)需擔(dān)憂對(duì)遺留代碼的影響,這是因?yàn)閷?shí)現(xiàn)更新接口的類現(xiàn)在會(huì)自動(dòng)繼承一個(gè)默認(rèn)的方法實(shí)現(xiàn)。

概述默認(rèn)方法

經(jīng)過(guò)前述的介紹,我們已經(jīng)了解了向已發(fā)布的API添加方法,對(duì)現(xiàn)存代碼實(shí)現(xiàn)會(huì)造成多大的損害。默認(rèn)方法是Java 8中引入的一個(gè)新特性,希望能借此以兼容的方式改進(jìn)API?,F(xiàn)在,接口包含的方法簽名在它的實(shí)現(xiàn)類中也可以不提供實(shí)現(xiàn)。那么,誰(shuí)來(lái)具體實(shí)現(xiàn)這些方法呢?實(shí)際上,缺失的方法實(shí)現(xiàn)會(huì)作為接口的一部分由實(shí)現(xiàn)類繼承(所以命名為默認(rèn)實(shí)現(xiàn)),而無(wú)需由實(shí)現(xiàn)類提供。

那么,我們?cè)撊绾伪孀R(shí)哪些是默認(rèn)方法呢?其實(shí)非常簡(jiǎn)單。默認(rèn)方法由 default 修飾符修飾,并像類中聲明的其他方法一樣包含方法體。比如,你可以像下面這樣在集合庫(kù)中定義一個(gè)名為Sized 的接口,在其中定義一個(gè)抽象方法 size ,以及一個(gè)默認(rèn)方法 isEmpty :

public interface Sized {
    int size();

    default boolean isEmpty() {
        return size() == 0;
    }
}

太棒了!這樣任何一個(gè)實(shí)現(xiàn)了 Sized 接口的類都會(huì)自動(dòng)繼承 isEmpty 的實(shí)現(xiàn)。因此,向提供了默認(rèn)實(shí)現(xiàn)的接口添加方法就不是源碼兼容的。

現(xiàn)在,我們回顧一下最初的例子,那個(gè)Java畫(huà)圖類庫(kù)和你的游戲程序。具體來(lái)說(shuō),為了以兼容的方式改進(jìn)這個(gè)庫(kù)(即使用該庫(kù)的用戶不需要修改他們實(shí)現(xiàn)了 Resizable 的類),可以使用默認(rèn)方法,提供 setRelativeSize 的默認(rèn)實(shí)現(xiàn):

default void setRelativeSize(int wFactor, int hFactor){
    setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);
}

由于接口現(xiàn)在可以提供帶實(shí)現(xiàn)的方法,是否這意味著Java已經(jīng)在某種程度上實(shí)現(xiàn)了多繼承?如果實(shí)現(xiàn)類也實(shí)現(xiàn)了同樣的方法,這時(shí)會(huì)發(fā)生什么情況?默認(rèn)方法會(huì)被覆蓋嗎?現(xiàn)在暫時(shí)無(wú)需擔(dān)心這些,Java 8中已經(jīng)定義了一些規(guī)則和機(jī)制來(lái)處理這些問(wèn)題。

你可能已經(jīng)猜到,默認(rèn)方法在Java 8的API中已經(jīng)大量地使用了。本章已經(jīng)介紹過(guò)我們前一章中大量使用的 Collection 接口的 stream 方法就是默認(rèn)方法。 List 接口的 sort 方法也是默認(rèn)方法。第3章介紹的很多函數(shù)式接口,比如 Predicate 、 Function 以及 Comparator 也引入了新的默認(rèn)方法,比如 Predicate.and 或者 Function.andThen (記住,函數(shù)式接口只包含一個(gè)抽象方法,默認(rèn)方法是種非抽象方法)。

默認(rèn)方法的使用模式

現(xiàn)在你已經(jīng)了解了默認(rèn)方法怎樣以兼容的方式演進(jìn)庫(kù)函數(shù)了。除了這種用例,還有其他場(chǎng)景也能利用這個(gè)新特性嗎?當(dāng)然有,你可以創(chuàng)建自己的接口,并為其提供默認(rèn)方法。這一節(jié)中,我們會(huì)介紹使用默認(rèn)方法的兩種用例:可選方法和行為的多繼承。

可選方法

你很可能也碰到過(guò)這種情況,類實(shí)現(xiàn)了接口,不過(guò)卻刻意地將一些方法的實(shí)現(xiàn)留白。我們以Iterator 接口為例來(lái)說(shuō)。 Iterator 接口定義了 hasNext 、 next ,還定義了 remove 方法。Java 8之前,由于用戶通常不會(huì)使用該方法, remove 方法常被忽略。因此,實(shí)現(xiàn) Interator 接口的類通常會(huì)為 remove 方法放置一個(gè)空的實(shí)現(xiàn),這些都是些毫無(wú)用處的模板代碼。

采用默認(rèn)方法之后,你可以為這種類型的方法提供一個(gè)默認(rèn)的實(shí)現(xiàn),這樣實(shí)體類就無(wú)需在自己的實(shí)現(xiàn)中顯式地提供一個(gè)空方法。比如,在Java 8中, Iterator 接口就為 remove 方法提供了一個(gè)默認(rèn)實(shí)現(xiàn),如下所示:

public interface Iterator {
    ...
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    ...
}

通過(guò)這種方式,你可以減少無(wú)效的模板代碼。實(shí)現(xiàn) Iterator 接口的每一個(gè)類都不需要再聲明一個(gè)空的 remove 方法了,因?yàn)樗F(xiàn)在已經(jīng)有一個(gè)默認(rèn)的實(shí)現(xiàn)。

行為的多繼承

默認(rèn)方法讓之前無(wú)法想象的事兒以一種優(yōu)雅的方式得以實(shí)現(xiàn),即行為的多繼承。這是一種讓類從多個(gè)來(lái)源重用代碼的能力。

Java的類只能繼承單一的類,但是一個(gè)類可以實(shí)現(xiàn)多接口。要確認(rèn)也很簡(jiǎn)單,下面是Java API中對(duì) ArrayList 類的定義:

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

類型的多繼承

這個(gè)例子中 ArrayList 繼承了一個(gè)類,實(shí)現(xiàn)了六個(gè)接口。因此 ArrayList 實(shí)際是七個(gè)類型的直接子類,分別是: AbstractList 、 List 、 RandomAccess 、 Cloneable 、 Serializable 、Iterable 和 Collection 。所以,在某種程度上,我們?cè)缇陀辛祟愋偷亩嗬^承。

由于Java 8中接口方法可以包含實(shí)現(xiàn),類可以從多個(gè)接口中繼承它們的行為(即實(shí)現(xiàn)的代碼)。讓我們從一個(gè)例子入手,看看如何充分利用這種能力來(lái)為我們服務(wù)。保持接口的精致性和正交性能幫助你在現(xiàn)有的代碼基上最大程度地實(shí)現(xiàn)代碼復(fù)用和行為組合。

利用正交方法的精簡(jiǎn)接口

假設(shè)你需要為你正在創(chuàng)建的游戲定義多個(gè)具有不同特質(zhì)的形狀。有的形狀需要調(diào)整大小,但是不需要有旋轉(zhuǎn)的功能;有的需要能旋轉(zhuǎn)和移動(dòng),但是不需要調(diào)整大小。這種情況下,你怎么設(shè)計(jì)才能盡可能地重用代碼?

你可以定義一個(gè)多帶帶的 Rotatable 接口,并提供兩個(gè)抽象方法 setRotationAngle 和getRotationAngle ,如下所示:

public interface Rotatable {
    int getRotationAngle();

    void setRotationAngle(int angleInDegrees);

    default void rotateBy(int angleInDegrees) {
        setRotationAngle((getRotationAngle() + angleInDegrees) % 360);
    }
}

這種方式和模板設(shè)計(jì)模式有些相似,都是以其他方法需要實(shí)現(xiàn)的方法定義好框架算法。

現(xiàn)在,實(shí)現(xiàn)了 Rotatable 的所有類都需要提供 setRotationAngle 和 getRotationAngle的實(shí)現(xiàn),但與此同時(shí)它們也會(huì)天然地繼承 rotateBy 的默認(rèn)實(shí)現(xiàn)。

類似地,你可以定義之前看到的兩個(gè)接口 Moveable 和 Resizable 。它們都包含了默認(rèn)實(shí)現(xiàn)。下面是 Moveable 的代碼:

public interface Moveable {
    int getX();

    void setX(int x);

    int getY();

    void setY(int y);

    default void moveHorizontally(int distance) {
        setX(getX() + distance);
    }

    default void moveVertically(int distance) {
        setY(getY() + distance);
    }
}

下面是 Resizable 的代碼:

public interface Resizable extends Drawable {
    int getWidth();

    void setWidth(int width);

    int getHeight();

    void setHeight(int height);

    void setAbsoluteSize(int width, int height);

    default void setRelativeSize(int wFactor, int hFactor){
        setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor);
    }
}

組合接口

通過(guò)組合這些接口,你現(xiàn)在可以為你的游戲創(chuàng)建不同的實(shí)體類。比如, Monster 可以移動(dòng)、旋轉(zhuǎn)和縮放。

public class Monster implements Rotatable, Moveable, Resizable {
    ...
}

Monster 類會(huì)自動(dòng)繼承 Rotatable 、 Moveable 和 Resizable 接口的默認(rèn)方法。這個(gè)例子中,Monster 繼承了 rotateBy 、 moveHorizontally 、 moveVertically 和 setRelativeSize 的實(shí)現(xiàn)。

你現(xiàn)在可以直接調(diào)用不同的方法:

Monster m = new Monster();
m.rotateBy(180);
m.moveVertically(10);

像你的游戲代碼那樣使用默認(rèn)實(shí)現(xiàn)來(lái)定義簡(jiǎn)單的接口還有另一個(gè)好處。假設(shè)你需要修改moveVertically 的實(shí)現(xiàn),讓它更高效地運(yùn)行。你可以在 Moveable 接口內(nèi)直接修改它的實(shí)現(xiàn),所有實(shí)現(xiàn)該接口的類會(huì)自動(dòng)繼承新的代碼(這里我們假設(shè)用戶并未定義自己的方法實(shí)現(xiàn))。

通過(guò)前面的介紹,你已經(jīng)了解了默認(rèn)方法多種強(qiáng)大的使用模式。不過(guò)也可能還有一些疑惑:如果一個(gè)類同時(shí)實(shí)現(xiàn)了兩個(gè)接口,這兩個(gè)接口恰巧又提供了同樣的默認(rèn)方法簽名,這時(shí)會(huì)發(fā)生什么情況?類會(huì)選擇使用哪一個(gè)方法?這些問(wèn)題,我們會(huì)在接下來(lái)的一節(jié)進(jìn)行討論。

解決沖突的規(guī)則

我們知道Java語(yǔ)言中一個(gè)類只能繼承一個(gè)父類,但是一個(gè)類可以實(shí)現(xiàn)多個(gè)接口。隨著默認(rèn)方法在Java 8中引入,有可能出現(xiàn)一個(gè)類繼承了多個(gè)方法而它們使用的卻是同樣的函數(shù)簽名。這種情況下,類會(huì)選擇使用哪一個(gè)函數(shù)?在實(shí)際情況中,像這樣的沖突可能極少發(fā)生,但是一旦發(fā)生這樣的狀況,必須要有一套規(guī)則來(lái)確定按照什么樣的約定處理這些沖突。這一節(jié)中,我們會(huì)介紹Java編譯器如何解決這種潛在的沖突。我們?cè)噲D回答像“接下來(lái)的代碼中,哪一個(gè) hello 方法是被 C 類調(diào)用的”這樣的問(wèn)題。注意,接下來(lái)的例子主要用于說(shuō)明容易出問(wèn)題的場(chǎng)景,并不表示這些場(chǎng)景在實(shí)際開(kāi)發(fā)過(guò)程中會(huì)經(jīng)常發(fā)生。

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}
public interface B extends A {
    default void hello() {
        System.out.println("Hello from B");
    }
}
public class C implements A, B {
    public static void main(String[] args) {
        // 猜猜打印的是什么?
        new C().hello();
    }
}

此外,你可能早就對(duì)C++語(yǔ)言中著名的菱形繼承問(wèn)題有所了解,菱形繼承問(wèn)題中一個(gè)類同時(shí)繼承了具有相同函數(shù)簽名的兩個(gè)方法。到底該選擇哪一個(gè)實(shí)現(xiàn)呢? Java 8也提供了解決這個(gè)問(wèn)題的方案。請(qǐng)接著閱讀下面的內(nèi)容。

解決問(wèn)題的三條規(guī)則

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

類中的方法優(yōu)先級(jí)最高。類或父類中聲明的方法的優(yōu)先級(jí)高于任何聲明為默認(rèn)方法的優(yōu)先級(jí)。

如果無(wú)法依據(jù)第一條進(jìn)行判斷,那么子接口的優(yōu)先級(jí)更高:函數(shù)簽名相同時(shí),優(yōu)先選擇擁有最具體實(shí)現(xiàn)的默認(rèn)方法的接口,即如果 B 繼承了 A ,那么 B 就比 A 更加具體。

最后,如果還是無(wú)法判斷,繼承了多個(gè)接口的類必須通過(guò)顯式覆蓋和調(diào)用期望的方法,顯式地選擇使用哪一個(gè)默認(rèn)方法的實(shí)現(xiàn)。

是的,就是這三條準(zhǔn)則就是你需要知道的全部了!

運(yùn)行結(jié)果

讓我們回顧一下開(kāi)頭的例子,這個(gè)例子中 C 類同時(shí)實(shí)現(xiàn)了 B 接口和 A 接口,而這兩個(gè)接口恰巧又都定義了名為 hello 的默認(rèn)方法。

編譯器會(huì)使用聲明的哪一個(gè) hello 方法呢?其實(shí)上面的代碼是編譯不通過(guò)的,按照規(guī)則(2),應(yīng)該選擇的是提供了最具體實(shí)現(xiàn)的默認(rèn)方法的接口。但,在C中不知道誰(shuí)比誰(shuí)更具體,所以需要顯示的指定調(diào)用哪個(gè)接口的方法:

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

    @Override
    public void hello() {
       A.super.hello();
    }

    OR

    @Override
    public void hello() {
       B.super.hello();
    }

    OR

    @Override
    public void hello() {
       System.out.println("Hello from C!");
    }
}

比如:調(diào)用 A.super.Hello(),那么打印的是 Hello form A!,調(diào)用 B.super.Hello() 那么輸出的是 Hello from B!。

如果,你碰到類似的問(wèn)題,以上的三條準(zhǔn)則將可以幫助你解決這個(gè)問(wèn)題!

小結(jié)

Java 8中的接口可以通過(guò)默認(rèn)方法和靜態(tài)方法提供方法的代碼實(shí)現(xiàn)。

默認(rèn)方法的開(kāi)頭以關(guān)鍵字 default 修飾,方法體與常規(guī)的類方法相同。

向發(fā)布的接口添加抽象方法不是源碼兼容的。

默認(rèn)方法的出現(xiàn)能幫助庫(kù)的設(shè)計(jì)者以后向兼容的方式演進(jìn)API。

默認(rèn)方法可以用于創(chuàng)建可選方法和行為的多繼承。

我們有辦法解決由于一個(gè)類從多個(gè)接口中繼承了擁有相同函數(shù)簽名的方法而導(dǎo)致的沖突。

類或者父類中聲明的方法的優(yōu)先級(jí)高于任何默認(rèn)方法。如果前一條無(wú)法解決沖突,那就選擇同函數(shù)簽名的方法中實(shí)現(xiàn)得最具體的那個(gè)接口的方法。

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

代碼

Github:chap9
Gitee:chap9

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71772.html

相關(guān)文章

  • 《java 8 實(shí)戰(zhàn)》讀書(shū)筆記 -九章 默認(rèn)方法

    摘要:類或父類中聲明的方法的優(yōu)先級(jí)高于任何聲明為默認(rèn)方法的優(yōu)先級(jí)。只有聲明了一個(gè)默認(rèn)方法。由于比更加具體,所以編譯器會(huì)選擇中聲明的默認(rèn)方法。 如果在現(xiàn)存的接口上引入了非常多的新方法,所有的實(shí)現(xiàn)類都必須進(jìn)行改造,實(shí)現(xiàn)新方法,為了解決這個(gè)問(wèn)題,Java 8為了解決這一問(wèn)題引入了一種新的機(jī)制。Java 8中的接口現(xiàn)在支持在聲明方法的同時(shí)提供實(shí)現(xiàn),這聽(tīng)起來(lái)讓人驚訝!通過(guò)兩種方式可以完成這種操作。其一...

    phoenixsky 評(píng)論0 收藏0
  • 流暢的python讀書(shū)筆記-九章-符合Python風(fēng)格的對(duì)象

    摘要:以便于用戶理解的方式返回對(duì)象的字符串表示形式。函數(shù)會(huì)調(diào)用函數(shù),對(duì)來(lái)說(shuō),輸出的是一個(gè)有序?qū)Α4送?,還有用于支持內(nèi)置的構(gòu)造函數(shù)的方法。可散列實(shí)現(xiàn)了方法,使用推薦的異或運(yùn)算符計(jì)算實(shí)例屬性的散列值私有屬性最好用命名規(guī)則來(lái)實(shí)現(xiàn)這種方式有好有壞 絕對(duì)不要使用兩個(gè)前導(dǎo)下劃線,這是很煩人的自私行為。——Ian Bicking 對(duì)象表示形式 repr()  以便于開(kāi)發(fā)者理解的方式返回對(duì)象的字符串表示形式...

    fai1017 評(píng)論0 收藏0
  • 【譯】 WebSocket 協(xié)議九章——擴(kuò)展(Extension)

    摘要:使用帶引號(hào)的語(yǔ)法變量時(shí),在引號(hào)字符后面的變量的值必須符合變量規(guī)范。可接受的擴(kuò)展標(biāo)頭字段的非規(guī)范性示例請(qǐng)注意,長(zhǎng)線被折疊以便于閱讀如下服務(wù)端接受一個(gè)或者多個(gè)擴(kuò)展字段,這些擴(kuò)展字段是包含客戶端請(qǐng)求的頭字段擴(kuò)展中的。 概述 本文為 WebSocket 協(xié)議的第九章,本文翻譯的主要內(nèi)容為 WebSocket 擴(kuò)展相關(guān)內(nèi)容。 擴(kuò)展(協(xié)議正文) WebSocket 可以請(qǐng)求該規(guī)范中提到的擴(kuò)展,We...

    Zoom 評(píng)論0 收藏0
  • Java8實(shí)戰(zhàn)》-讀書(shū)筆記第一章(02)

    摘要:實(shí)戰(zhàn)讀書(shū)筆記第一章從方法傳遞到接著上次的,繼續(xù)來(lái)了解一下,如果繼續(xù)簡(jiǎn)化代碼。去掉并且生成的數(shù)字是萬(wàn),所消耗的時(shí)間循序流并行流至于為什么有時(shí)候并行流效率比循序流還低,這個(gè)以后的文章會(huì)解釋。 《Java8實(shí)戰(zhàn)》-讀書(shū)筆記第一章(02) 從方法傳遞到Lambda 接著上次的Predicate,繼續(xù)來(lái)了解一下,如果繼續(xù)簡(jiǎn)化代碼。 把方法作為值來(lái)傳遞雖然很有用,但是要是有很多類似與isHeavy...

    lushan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<