Java 8預設方法會破壞你的(使用者的)程式碼
Java 8的預設方法試圖嘗試更進一步簡化Java API。不幸的是,這一最近的語言擴充套件帶來了一系列複雜的規則,但只有少部分Java開發者意識到這一點。這篇文章告訴你為什麼引入預設方法會破壞你的(使用者的)程式碼。
起初看來,預設方法給Java虛擬機器的指令集帶來了很多新的特性。最終,開發庫的人能夠在不帶來客戶端程式碼的相容性問題的情況下,升級API。使用預設方法,任何實現庫介面的類都自動適應介面引入的預設方法。一旦使用者更新了他實現的類,就能夠很簡單使用更有意義的方法來覆蓋原有預設方法。更好的是,使用者可以在覆蓋方法時候,呼叫介面的預設實現,同時增加業務邏輯。
到現在為止,一切都是很好。但是,在建立介面的時候增加預設方法可能使得Java程式碼不相容。這個從下面的例子可以很容易弄明白。我們假設一個庫需要它的一個介面的作為輸入:
interface SimpleInput { void foo(); void bar(); }
abstract class SimpleInputAdapter implements SimpleInput { @Override public void bar() { // some default behavior ... } }
Java 8之前,類似於上面聯合使用一個介面和一個介面卡類的方式,是Java程式語言中一種非常常用的設計模式。該介面卡通常由庫提供者提供,用於節省庫的使用者的某些操作。但是,如果採用介面的方式提供,就類似允許多重繼承了。
我們進一步假設一個使用者使用瞭如下的介面卡:
class MyInput extends SimpleInputAdapter { @Override public void foo() { // do something ... } @Override public void bar() { super.bar(); // do something additionally ... } }
通過這種實現方式,我們最終可以和庫進行互動。注意我們是怎樣覆蓋bar方法,併為預設的實現增加額外的功能的。
如果將該庫移植到Java 8,將會發生什麼呢?首先,該庫很大可能性會廢棄介面卡類,而使用預設方法提供該功能。最終,該介面的形式類似如下所示:
interface SimpleInput { void foo(); default void bar() { // some default behavior } }
使用這個新的介面,使用者可以更新他的程式碼,採用預設方法來代替原來的介面卡類。通過使用介面代替介面卡類的最好的結果是,該類可以繼承(extend)其它的類,而不是特定的介面卡。現在我們進行實踐,移植MyInput類使其使用預設方法。因為我們現在能繼承其它類了,所以我們繼承一個第三方的基礎類。我們這裡不需要關心這個基礎類的作用,我們可以假設這個對我們的功能是有意義的。
class MyInput extends ThirdPartyBaseClass implements SimpleInput { @Override public void foo() { // do something ... } @Override public void bar() { SimpleInput.super.bar(); // do something additionally ... } }
為了實現原始類相似的功能,我們使用Java 8的新的語法來呼叫指定介面的預設方法。同時,將我們方法中的一些邏輯移到基礎類中去。此時,你可能拍著我的肩膀說,這是一次非常好的重構!
我們相當成功的使用了該庫。但是,維護人員需要增加另一個介面來提供更多的功能。該介面被 ComplexInput 介面所代替,這個介面繼承自 SimpleInput 介面,並增加了新的方法。因為預設方法通常來說是可以很安全的新增的,因此,維護人員覆蓋了 SimpleInput 的預設方法,提供了一個更好的預設方法。畢竟,這對於採用介面卡類的方式來說是很平常的事情。
interface ComplexInput extends SimpleInput { void qux(); @Override default void bar() { SimpleInput.super.bar(); // so complex, we need to do more ... } }
新的特性帶來了非常好的效果以至於維護 ThirdPartyBaseClass 的人也決定依賴該庫。為了完成這項工作,它在 ThirdPartyLibrary 中實現了 ComplexInput 介面。
但是這對 MyInput 類來說意味著什麼呢?為了隱式的實現 ComplexInput 介面,可繼承 ThirdPartyBaseClass 類,但是呼叫 SimpleInput 的預設方法突然變成非法的了。結果,使用者的程式碼不能通過編譯。現在這種呼叫是被禁止的,因為Java認為這種在非直接子類中呼叫父類的父類的方法是非法的。你只能在 ComplexInput 中去呼叫該預設方法,但是,這要求你顯示的在MyInput中實現該介面。對於庫的使用者來說,這種改變不是所預期的!
更奇怪的是,Java執行時卻不做這種限制。JVM的校驗器是允許一個編譯好的類去呼叫 SimpleInput::foo 方法的,即使該類是通過繼承更新後的 ThirdPartyBaseClass,從而隱式的實現了ComplexClass。這種限制只存在於編譯器中。
我們從這裡能學到什麼東西呢?簡單的說,確保不要在一個介面中覆蓋另一個介面的預設方法,既不要用預設方法覆蓋,也不要用抽象方法覆蓋。總的來說,請謹慎使用預設方法。即使它使得Java的集合介面API輕易的發生了革命性的變化,但本質上講,這種繼承層級之間的方法呼叫,增加系統的複雜性。而在Java 7之前,你只需要沿著線性的類層級去查詢真正呼叫的程式碼。只有當你覺得非常有必要的時候才去增加這種複雜性。
相關文章
- Java 8 介面裡的預設方法特性Java
- Java8 預設方法Java
- 怎樣保證我的程式碼不會被別人破壞?
- Java 8中Lambda表示式預設方法的模板方法模式,你夠了解麼?Java模式
- 【Java8新特性】介面中的預設方法和靜態方法,你都掌握了嗎?Java
- 消滅 Java 程式碼的“壞味道”Java
- java8 新特性之預設方法Java
- java8函數語言程式設計筆記-破壞式更新和函式式更新Java函數程式設計筆記函式
- 幹掉你程式碼中的壞味道
- Java8 新特性之預設介面方法Java
- 一文帶你認識Java8中介面的預設方法Java
- java8介面的靜態方法和預設方法Java
- Java 8 預設方法和多繼承深入解析Java繼承
- BIOS被病毒破壞了的解決方法(轉)iOS
- Java8新特性第2章(介面預設方法)Java
- 檔案系統被破壞時的處理方法(轉)
- 控制檔案被破壞的資料庫恢復方法資料庫
- Java 程式的破解方法 (8千字)Java
- 8個方法讓你成為更優秀的程式設計師程式設計師
- C# 8: 預設介面方法C#
- java8預設使用的垃圾收集器Java
- 聊一聊Java8 Optional,讓你的程式碼更加優雅Java
- 程式碼的壞味道「上」
- (驚呆)java反序列化漏洞—被低估的破壞之王Java
- 破譯替換密碼的方法密碼
- win8系統設定預設印表機的快速方法
- 設定myeclipse的預設編碼為utf-8Eclipse
- 22種程式碼的壞味道
- 程式設計師的35個壞習慣,你有幾條?程式設計師
- 證明你是壞程式設計師的7個跡象程式設計師
- 為什麼有人說無程式碼和低程式碼軟體行業破壞者?行業
- win10預設程式怎麼更改_win10設定預設程式的方法Win10
- 安裝Fedora 22破壞了原Win10系統的UEFI載入程式的解決方法Win10
- Linux檔案系統被破壞時的處理方法(轉)Linux
- 新的TLS加密破壞攻擊也會影響新的TLS 1.3協議TLS加密協議
- Java 開發者的程式設計噩夢,為什麼你的程式碼總有 bug??Java程式設計
- Oracle的預設使用者及其建立指令碼[轉]Oracle指令碼
- oracle BI工具的預設使用者和密碼Oracle密碼