轉(zhuǎn)帖|行業(yè)資訊|編輯:龔雪|2014-05-29 09:45:57.000|閱讀 509 次
概述:深度解析Java 8默認(rèn)方法,了解Java 8默認(rèn)方法致代碼不兼容的情況。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
默認(rèn)方法給JVM的指令集增加了一個非常不錯的新特性。使用了默認(rèn)方法之后,如果庫中的接口增加了新的方法,實現(xiàn)了這個接口的用戶類能夠自動獲得這個方法的默認(rèn)實現(xiàn)。一旦用戶想更新他的實現(xiàn)類的話,只需覆蓋一下這個默認(rèn)方法就可以了,取而代之的是一個在特定場景下更有意義的實現(xiàn)。更棒的是,用戶可以在重寫的方法里面調(diào)用接口的默認(rèn)實現(xiàn)來增加一些額外的功能。
目前為止一切都還不錯。然而,給現(xiàn)有的Java接口增加默認(rèn)方法可能會導(dǎo)致代碼的不兼容??磦€例子就很容易能明白了。假設(shè)有一個庫,它需要用戶實現(xiàn)它的一個接口作為輸入:
interface SimpleInput { void foo(); void bar(); } abstract class SimpleInputAdapter implements SimpleInput { @Override public void bar() { // some default behavior ... } }
在Java 8以前,上述這種接口和一個對應(yīng)的適配器類的組合在Java語言中是一種很常見的模式。類庫的開發(fā)人員提供了一個適配器來減少庫使用者的編碼量。然而提供這個接口的目的其實是為了能實現(xiàn)某種類似多重繼承的關(guān)系。
我們假設(shè)有一個用戶使用了這個適配器:
class MyInput extends SimpleInputAdapter{ @Override public void foo() { // do something ... } @Override public void bar() { super.bar(); // do something additionally ... } }
有了這個實現(xiàn),用戶可以和庫進行交互了。注意這個實現(xiàn)是如何重寫bar方法來給默認(rèn)的實現(xiàn)增加額外的功能的。
那如果這個庫遷移到Java 8的話會怎樣?首先,這個庫很可能會廢棄掉這個適配器類并將這個功能遷移到默認(rèn)方法里。最終這個接口看起來會是這樣的:
interface SimpleInput {
void foo();
default void bar() {
// some default behavior
}
}
有了這個新接口后,用戶得更新他的代碼來使用這個默認(rèn)方法,而不再是適配器類了。使用新接口而非適配器類的一大好處就是,用戶可以去繼承一個別的類而不是這個適配器類了。我們來動手實踐一下,將MyInput類改造成使用默認(rèn)方法。由于現(xiàn)在我們可以繼承別的類了,我們再額外地擴展一個第三方的基類試試。這個基類具體是做什么的在這里并不重要,我們先假設(shè)一下這么做對我們這個用例來說是有意義的。
class MyInput extends ThirdPartyBaseClass implements SimpleInput { @Override public void foo() { // do something ... } @Override public void bar() { SimpleInput.super.foo(); // do something additionally ... } }
為了實現(xiàn)和原先那個類同樣的功能,這里我們用到了Java 8的新語法來調(diào)用接口的默認(rèn)方法。同樣的,我們把myMethod的邏輯放到某個基類MyBase里面??梢源反芳绨蚍潘上铝恕V貥?gòu)之后棒極了!
我們使用的這個庫得到了很大的改進。然而,維護人員需要添加另一個接口來實現(xiàn)一些額外的功能。這個接口叫做CompexInput ,它繼承了SimpleInput類,并增加了一個額外的方法。由于通常都認(rèn)為默認(rèn)方法是可以放心地添加的,因此維護人員重寫了SimpleInput類的默認(rèn)方法并添加了一些額外的動作來給用戶提供一個更好的默認(rèn)實現(xiàn)。畢竟使用適配器類的時候這個做法也十分常見:
interface ComplexInput extends SimpleInput { void qux(); @Override default void bar() { SimpleInput.super.bar(); // so complex, we need to do more ... } }
這個新特性看起來非常不錯,因此ThirdPartyBaseClass類的維護人員也決定使用這個庫了。為了實現(xiàn)這個,他將ThirdPartyBaseClass類實現(xiàn)了ComplexInput接口。
但這樣的話對MyInput類意味著什么?由于它繼承了ThirdPartyBaseClass類,因此默認(rèn)實現(xiàn)了ComplexInput接口,這樣的話調(diào)用SimpleInput的默認(rèn)方法就不合法了。結(jié)果就是,用戶的代碼最后無法通過編譯。還有就是,現(xiàn)在已經(jīng)徹底無法調(diào)用這個方法了,因為Java把這種調(diào)用間接父類的super-super方法認(rèn)為是不合法的。你只能去調(diào)用ComplexInput接口的默認(rèn)方法了。然而這首先需要你在MyInput類中顯式的實現(xiàn)一下這個接口。對于這個庫的用戶而言,這些改動完全是意想不到的。
(注:簡單點說其實就是:
interface A { default void test() { } } interface B extends A { default void test() { } } public class Test implements B { public void test() { B.super.test(); //A.super.test(); 錯誤 } }
當(dāng)然這么寫的話是用戶主動選擇實現(xiàn)了B接口,而文中的例子由于引入了一個基類,因此由于庫和基類中都進行了一個看似沒有影響的改動,實際上卻導(dǎo)致用戶代碼無法通過編譯)
很奇怪的是,Java在運行時并沒有對這個進行區(qū)分。JVM的校驗器允許一個編譯過的類進行SimpleInput::foo方法的調(diào)用,盡管加載的這個類繼承了ThirdPartyBaseClass的更新版本后隱式地實現(xiàn)了ComplexInput接口。要怪只能怪編譯器了。(注:編譯器與運行時的行為不一致)
那我們從中學(xué)到了什么?簡單地說,不要在另一個接口中重寫原接口的默認(rèn)方法。不要用另一個默認(rèn)方法來重寫它,也不要某個抽象方法來重寫它??偠灾褂媚J(rèn)方法時應(yīng)當(dāng)十分謹(jǐn)慎。雖然它們使得Java現(xiàn)有的集合庫的接口更容易改進了,但它允許你在類的繼承結(jié)構(gòu)中進行方法調(diào)用,這本質(zhì)上其實是增加了復(fù)雜性。在Java 7以前,你只需遍歷線性的類層次結(jié)構(gòu)看一下實際調(diào)用的代碼就可以了。當(dāng)你覺得的確需要的時候,再去使用默認(rèn)方法。
溫馨小提示:好的資源,在 Java開發(fā)中能事半功倍!
原文轉(zhuǎn)載至://it.deepinmind.com/java/2014/05/19/java-8-default-methods-can-break-your-code.html
業(yè)界被公認(rèn)為最好的Java開發(fā)平臺:IntelliJ IDEA
最實惠、綜合全面的J2EE IDE與Web開發(fā)工具套件:MyEclipse
多平臺Java安裝文件生成工具:install4j
全面測試Java程序的工具:Parasoft Jtest
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請郵件反饋至chenjj@fc6vip.cn