您需要了解的有关默认方法的所有信息
因此,默認(rèn)方法是……昨天的新聞,對不對? 是的,但是使用一年后,積累了很多事實(shí),我想將這些事實(shí)收集在一個地方,供剛開始使用它們的開發(fā)人員使用。 甚至有經(jīng)驗(yàn)的人都可以找到他們不知道的一兩個細(xì)節(jié)。
如果有新發(fā)現(xiàn),我將在將來擴(kuò)展此職位。 因此,我要我的讀者(是的,你們兩個!)向我提供有關(guān)默認(rèn)方法的每個小事實(shí),您在這里找不到這些方法。 如果您有任何東西,請鳴叫 , 發(fā)郵件或發(fā)表評論。
總覽
我想我沒有給這篇文章一個有意義的敘述。 原因在于,從本質(zhì)上講,這是一篇Wiki文章。 它涵蓋了默認(rèn)方法的不同概念和細(xì)節(jié),盡管它們與自然相關(guān),但它們并不適合進(jìn)行連續(xù)敘述。
但這也有好處! 您可以輕松地在文章中跳來跳去,而不會大大降低您的閱讀體驗(yàn)。 查看目錄,以全面了解所涵蓋的內(nèi)容,并了解您的好奇心將您帶到何處。
默認(rèn)方法
到現(xiàn)在為止,大多數(shù)開發(fā)人員將已經(jīng)使用,閱讀甚至實(shí)現(xiàn)默認(rèn)方法,因此,我將為每個人省去語法的詳細(xì)介紹。 在介紹更廣泛的概念之前,我將花更多時間在它的細(xì)節(jié)上。
句法
默認(rèn)方法的新語言功能歸結(jié)為接口現(xiàn)在可以聲明非抽象方法,即帶有主體的方法。
以下示例是JDK 8中Comparator.thenComparing(Comparator)( link )的修改版:
比較器中的默認(rèn)方法
default Comparator<T> thenComparing(Comparator<? super T> other) {return (o1, o2) -> {int res = this.compare(o1, o2);return (res != 0) ? res : other.compare(o1, o2);}; }除了關(guān)鍵字default之外,這看起來像一個“常規(guī)”方法聲明。 將這樣的方法添加到接口而沒有編譯錯誤并提示方法調(diào)用解決策略是必要的。
現(xiàn)在,實(shí)現(xiàn)Comparator每個類都將包含thenComparing(Comparator)公共方法,而不必自己實(shí)現(xiàn)-可以這么說,它是免費(fèi)的。
顯式調(diào)用默認(rèn)方法
在下面的內(nèi)容中,我們將看到一些人們可能想要從某個特定的超級接口顯式調(diào)用方法的默認(rèn)實(shí)現(xiàn)的原因。 如果有需要,請按照以下步驟進(jìn)行:
明確調(diào)用默認(rèn)實(shí)現(xiàn)
class StringComparator implements Comparator<String> {// ...@Overridepublic Comparator<String> thenComparing(Comparator<? super String> other) {log("Call to 'thenComparing'.");return Comparator.super.thenComparing(other);} }請注意,接口的名稱如何用于指定以下super ,否則將引用超類(在本例中為Object )。 從句法上講,這類似于可以從嵌套類訪問對外部類的引用 。
解決策略
因此,讓我們考慮一個使用默認(rèn)方法實(shí)現(xiàn)接口的類型的實(shí)例。 如果調(diào)用存在默認(rèn)實(shí)現(xiàn)的方法會怎樣? (請注意,方法由其簽名標(biāo)識,該簽名包括名稱和參數(shù)類型。)
規(guī)則1 :Brian Goetz – 2013年3月3日(格式化我的)
首先,這闡明了為什么將這些方法稱為默認(rèn)方法以及為什么必須使用關(guān)鍵字default啟動它們:
這樣的實(shí)現(xiàn)是備用的,以防萬一一個類并且其超類都不考慮該方法,即不提供任何實(shí)現(xiàn),也不將其聲明為抽象(請參見規(guī)則#1 )。 等效地,僅在類未實(shí)現(xiàn)擴(kuò)展X并聲明相同方法的接口Y (默認(rèn)或抽象;請參見規(guī)則#2 )時,才使用接口X的默認(rèn)方法。
盡管這些規(guī)則很簡單,但是它們并不能阻止開發(fā)人員創(chuàng)建復(fù)雜的情況。 這篇文章提供了一個示例,其中分辨率的預(yù)測并非微不足道,并提出了應(yīng)謹(jǐn)慎使用此功能的說法。
解決策略包含一些有趣的細(xì)節(jié)……
解決沖突
規(guī)則#3 ,或者說它的缺失,意味著具體的類必須實(shí)現(xiàn)存在競爭默認(rèn)實(shí)現(xiàn)的每個方法。 否則,編譯器將引發(fā)錯誤。 如果有競爭的實(shí)現(xiàn)之一是合適的,則方法主體可以顯式調(diào)用該方法 。
這也意味著向接口添加默認(rèn)實(shí)現(xiàn)會導(dǎo)致編譯錯誤。 如果一個類A實(shí)現(xiàn)無關(guān)接口X和Y并且其已經(jīng)存在于默認(rèn)方法X被添加到Y(jié) ,類A將不再編譯。
如果未A , X和Y一起編譯,并且JVM偶然發(fā)現(xiàn)這種情況,會發(fā)生什么情況? 有趣的問題, 答案似乎還不清楚 。 看起來JVM將拋出IncompatibleClassChangeError。
重新提取方法
如果抽象類或接口A某個方法聲明為抽象,而某些超級接口X存在默認(rèn)實(shí)現(xiàn),則X的默認(rèn)實(shí)現(xiàn)將被覆蓋。 因此,所有子類A具體類都必須實(shí)現(xiàn)該方法。 這可以用作強(qiáng)制執(zhí)行不合適的默認(rèn)實(shí)現(xiàn)的有效工具。
此技術(shù)在整個JDK中使用,例如在ConcurrentMap ( link )上使用,該方法重新抽象了Map ( link )提供默認(rèn)實(shí)現(xiàn)的許多方法,因?yàn)樗鼈儾皇蔷€程安全的(搜索術(shù)語“不合適的默認(rèn)值”)。
請注意,具體類仍可以選擇顯式調(diào)用重寫的默認(rèn)實(shí)現(xiàn) 。
“對象”上的覆蓋方法
接口不可能為Object的方法提供默認(rèn)實(shí)現(xiàn)。 嘗試這樣做將導(dǎo)致編譯錯誤。 為什么?
首先,這將是無用的。 由于每個類都繼承自O(shè)bject ,因此規(guī)則1明確表示永遠(yuǎn)不會調(diào)用這些方法。
但是那條規(guī)則不是自然法則,專家組本可以例外。 郵件中還包含規(guī)則, Brian Goetz給出了為什么沒有規(guī)則的許多原因 。 我最喜歡的一個(格式化我的):
從根本toString ,來自O(shè)bject的方法(例如toString , equals和hashCode )都是關(guān)于對象的state的 。 但是接口沒有狀態(tài)。 類有狀態(tài)。 這些方法屬于擁有對象狀態(tài)的代碼-類。
修飾符
請注意,有很多修飾符不能在默認(rèn)方法上使用:
- 可見性固定為公開(與其他界面方法一樣)
- 關(guān)鍵字synchronized禁止(如在抽象方法)
- 關(guān)鍵字final是被禁止的(與抽象方法一樣)
當(dāng)然,需要這些功能,并且對它們的缺失進(jìn)行了全面的解釋(例如,針對final和synced )。 參數(shù)總是相似的:這不是默認(rèn)方法的目的 ,引入這些功能將導(dǎo)致更復(fù)雜且易于出錯的語言規(guī)則和/或代碼。
不過,您可以使用static ,這將減少對復(fù)數(shù)形式的實(shí)用程序類的需求 。
一點(diǎn)上下文
現(xiàn)在我們已經(jīng)知道了如何使用默認(rèn)方法,下面將這些知識放到上下文中。
由F_A在CC-BY 2.0下發(fā)布 。
接口演變
經(jīng)常可以找到介紹默認(rèn)方法的專家組,他們指出他們的目標(biāo)是允許“接口演變”:
默認(rèn)方法 […]的目的是使接口在首次發(fā)布后能夠以兼容的方式進(jìn)行開發(fā)。
Brian Goetz – 2013年9月
在使用默認(rèn)方法之前,幾乎不可能(不包括某些組織模式;請參閱此概述 )在不破壞所有實(shí)現(xiàn)的情況下向接口添加方法。 盡管這對于控制這些實(shí)現(xiàn)的絕大多數(shù)軟件開發(fā)人員都無關(guān)緊要,但對于API設(shè)計人員而言,這是一個至關(guān)重要的問題。 Java始終保持安全,在發(fā)布接口后也從未更改過接口。
但是隨著引入lambda表達(dá)式,這變得難以忍受。 想象一下總是編寫Stream.of(myList).forEach(...)的集體痛苦,因?yàn)闊o法將forEach添加到List 。
因此,引入了lambdas的專家組決定尋找一種方法,以在不破壞任何現(xiàn)有實(shí)現(xiàn)的情況下實(shí)現(xiàn)接口演化。 他們對這一目標(biāo)的關(guān)注解釋了默認(rèn)方法的特征 。
在小組認(rèn)為有可能不降低該主要用例的可用性的情況下,他們還啟用了使用默認(rèn)方法來創(chuàng)建特征的方法,或者甚至是接近它們的方法。 盡管如此,他們還是因?yàn)闆]有“一路走到”混合蛋白和特質(zhì)而經(jīng)常受到攻擊,對此人們經(jīng)常重復(fù)回答:“是的,因?yàn)槟遣皇俏覀兊哪繕?biāo)。”
實(shí)用工具類
JDK以及特別常見的輔助庫(例如Guava和Apache Commons)充滿了實(shí)用程序類。 它們的名稱通常是為其提供方法的接口的復(fù)數(shù)形式,例如Collections或Sets 。 它們存在的主要原因是那些實(shí)用程序方法在發(fā)布后不能添加到原始接口中。 使用默認(rèn)方法,這成為可能。
現(xiàn)在,所有將接口實(shí)例作為參數(shù)的靜態(tài)方法都可以轉(zhuǎn)換為接口上的默認(rèn)方法。 例如,查看靜態(tài)Collections.sort(List) ( link ),從Java 8開始,它靜態(tài)地委派給新實(shí)例默認(rèn)方法List.sort(Comparator) ( link )。 我的帖子中給出了另一個示例, 說明如何使用默認(rèn)方法來改進(jìn)裝飾器模式 。 其他不帶參數(shù)的實(shí)用程序方法(通常是生成器)現(xiàn)在可以成為接口上的靜態(tài)默認(rèn)方法。
雖然可以在代碼庫中刪除所有與接口相關(guān)的實(shí)用程序類,但建議不要這樣做。 界面的可用性和內(nèi)聚性應(yīng)該仍然是主要優(yōu)先事項,而不是在其中填充所有可想象的功能。 我的猜測是,只有將這些方法中最通用的方法移到接口上,而在一個(或多個?)實(shí)用工具類中可以保留更多晦澀的操作,這才有意義。 (或完全刪除它們 ,如果您愿意的話。)
分類
在對新Javadoc標(biāo)簽的爭論中,Brian Goetz對迄今為止已引入JDK(格式化我的格式)的默認(rèn)方法進(jìn)行了弱分類:
1.可選方法 :它遵守合同,因?yàn)楹贤@然是薄弱的,但是任何關(guān)心刪除的類肯定會覆蓋它。
對于大多數(shù)實(shí)現(xiàn)來說,這種實(shí)現(xiàn)是完美的,但是如果某些類(例如ArrayList )的維護(hù)者有足夠的動力去做的話,可能會有做得更好的機(jī)會。 Map上的新方法(例如putIfAbsent )也在此存儲桶中。
Brian Goetz – 2013年1月31日
我將此分類稱為“弱”分類,因?yàn)樗匀蝗狈﹃P(guān)于在何處放置方法的硬性規(guī)定。 但是,這并沒有使它無用。 恰恰相反,我認(rèn)為這對交流它們有很大幫助,在閱讀或編寫默認(rèn)方法時要牢記一件好事。
文獻(xiàn)資料
請注意,默認(rèn)方法是引入新的(非正式)Javadoc標(biāo)記@apiNote , @implSpec和@implNote的主要原因 。 JDK經(jīng)常使用它們,因此了解它們的含義很重要。 了解它們的一個好方法是閱讀我的上一篇文章 (平穩(wěn),對嗎?),其中詳細(xì)介紹了它們。
繼承與建立類
繼承的不同方面以及如何使用它來構(gòu)建類經(jīng)常在關(guān)于默認(rèn)方法的討論中出現(xiàn)。 讓我們仔細(xì)看看它們,看看它們與新語言功能的關(guān)系。
多重繼承-什么?
通過繼承,一個類型可以假定另一個類型的特征。 存在三種特征:
- 類型 ,即通過子類型化類型是另一種類型
- 行為 ,即一種類型繼承方法,因此其行為與另一種類型相同
- state ,即一個類型繼承了定義另一個類型狀態(tài)的變量
由于類是其父類的子類型,并且繼承了所有方法和變量,因此類繼承顯然涵蓋了所有這三個特征。 同時,一個類只能擴(kuò)展另一個類,因此僅限于單一繼承。
接口是不同的:一個類型可以繼承許多接口,并成為每個接口的子類型。 因此,從第一天開始,Java就一直支持這種多重繼承。
但是在Java 8之前,實(shí)現(xiàn)類僅繼承了接口的類型。 是的,它也繼承了合同,但沒有繼承其實(shí)際實(shí)施,因此它必須提供自己的行為。 使用默認(rèn)方法時,此更改發(fā)生了變化,因此從Java的版本8開始,還支持行為的多重繼承。
Java仍然沒有提供明確的方法來繼承多種類型的狀態(tài)。 但是,使用惡意方法或虛擬字段模式可以通過默認(rèn)方法實(shí)現(xiàn)類似的效果。 前者很危險,不應(yīng)該使用,后者也有一些缺點(diǎn)(特別是在封裝方面),應(yīng)謹(jǐn)慎使用。
默認(rèn)方法與混合和特質(zhì)
在討論默認(rèn)方法時,有時會將它們與mixins和traits進(jìn)行比較。 本文無法詳細(xì)介紹這些內(nèi)容,但將粗略介紹它們與具有默認(rèn)方法的接口的區(qū)別。 (可以在StackOverflow上找到mixins和trait的有用比較。)
混合蛋白
Mixins允許繼承其類型,行為和狀態(tài)。 一個類型可以從多個mixin繼承,從而提供所有三個特征的多重繼承。 根據(jù)語言的不同,也許還可以在運(yùn)行時將混合添加到單個實(shí)例。
由于具有默認(rèn)方法的接口不允許狀態(tài)的繼承,因此顯然它們不是mixin。
特質(zhì)
與mixin相似,特征允許類型(和實(shí)例)從多個特征繼承。 它們還繼承了它們的類型和行為,但是與混合混合不同,常規(guī)特征沒有定義自己的狀態(tài)。
這使得特征類似于具有默認(rèn)方法的接口。 概念仍然不同,但是這些差異并非完全無關(guān)緊要。 將來我可能會再次討論并進(jìn)行更詳細(xì)的比較,但是在那之前,我將為您提供一些建議:
- 如我們所見, 方法調(diào)用解析并不總是那么瑣碎,這會使快速使用默認(rèn)方法的不同接口的交互成為復(fù)雜性的負(fù)擔(dān)。 性狀通常以一種或另一種方式緩解此問題。
- 特性允許Java不完全支持的某些操作。 請參閱有關(guān)特質(zhì)的Wikipedia文章中的“選擇操作”后的項目符號列表。
- 論文“ Java 8中的面向特征的編程”探討了使用默認(rèn)方法的面向特征的編程風(fēng)格,并遇到了一些問題。
因此,盡管具有默認(rèn)方法的接口沒有任何特征,但是相似性允許像以前那樣以有限的方式使用它們。 這與專家組的設(shè)計目標(biāo)一致, 該目標(biāo)試圖在不與原始目標(biāo)沖突的地方適應(yīng)這種用例,即界面的發(fā)展和易用性。
默認(rèn)方法與抽象類
現(xiàn)在,接口可以提供行為,它們可以進(jìn)入抽象類的領(lǐng)域,很快就會出現(xiàn)問題,可以在給定的情況下使用。
語言差異
首先讓我們說明一下語言層面的一些差異:
盡管接口允許多重繼承,但它們基本上在類構(gòu)建的其他各個方面都達(dá)不到要求。 默認(rèn)方法永遠(yuǎn)不會是最終方法,無法同步并且不能覆蓋Object的方法。 它們始終是公共的,這嚴(yán)重限制了編寫簡短且可重用方法的能力。 此外,接口仍無法定義字段,因此每個狀態(tài)更改都必須通過公共API進(jìn)行。 為適應(yīng)該用例而對API進(jìn)行的更改通常會破壞封裝。
盡管如此,仍然存在一些用例,其中這些差異無關(guān)緊要,并且兩種方法在技術(shù)上都是可行的。
概念差異
然后是概念上的差異。 類定義什么是什么,而接口通常定義什么可以做 。
抽象類是完全特殊的東西。 有效的Java項目18全面解釋了為什么接口在定義具有多個子類型的類型時優(yōu)于抽象類。 (而且這甚至沒有考慮默認(rèn)方法。)要點(diǎn)是:抽象類對于接口的骨架(即部分)實(shí)現(xiàn)有效,但如果沒有匹配的接口就不應(yīng)存在。
因此,當(dāng)有效地將抽象類簡化為低可見性,接口的基本實(shí)現(xiàn)時,默認(rèn)方法是否也可以消除這種情況? 決定: 不! 實(shí)現(xiàn)接口幾乎總是需要某些或所有默認(rèn)方法所缺少的類構(gòu)建工具。 而且,如果某些界面沒有,那顯然是一種特殊情況,這不會使您誤入歧途。 (有關(guān)使用默認(rèn)方法實(shí)現(xiàn)接口時可能發(fā)生的情況,請參見此早期文章 。)
更多連結(jié)
- Lambda狀態(tài)的最終版本(第10章介紹了默認(rèn)方法)
- 官方教程
- 關(guān)于如何發(fā)展接口的官方教程
- JavaCodeGeeks教程
- DZone教程
反射
這篇文章應(yīng)該都談過了一個需要了解的默認(rèn)方法。 如果您不同意,請鳴叫 , 發(fā)郵件或發(fā)表評論。 批準(zhǔn)和+1也可以接受。
翻譯自: https://www.javacodegeeks.com/2015/02/everything-you-need-to-know-about-default-methods.html
總結(jié)
以上是生活随笔為你收集整理的您需要了解的有关默认方法的所有信息的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Nifty JUnit:在方法和类级别上
- 下一篇: OSGi服务测试助手:ServiceRe