Java 8 初探 - Default Methods

Java 8 初探 - Default Methods

更新於 發佈於 閱讀時間約 6 分鐘
此為過去的舊文,2014 年 5 月 3 日初次發表於 logdown。

今天離開 Lambda 相關的議題,看一下 Java 8 另一個和語言相關的改變:default methods。過去,Java 的介面只能宣告函式卻不能提供實作 (或是說是定義函式,定義函式和宣告函式的差別就是有沒有實作),因此大多數的 library 設計都將工具函式 (static utility methods) 放在類別中,這基本上沒什麼大問題,但是對有些潔癖的人來說,就會考慮要不要把該類別的建構子設為 private?

另外,Java 不支援多重繼承,但能實作多個介面,所以在Java的函式庫裡常會看到一種設計慣例:介面宣告需要的函式,透過抽象類別提供共用的預設實作,最後由預設類別提供完整的實作,例如:TableModel 宣告 JTable 所需要的最小函式集合,AbstractTableModel 提供部分共用的實作 (listeners的管理),最後 DefaultTableModel 提供一個完整的實作。這樣的慣例引起另一個問題:但是當 A 類別繼承了 B 類別,但又想使用 C 介面的抽象類別實作就沒辦法了 (因為不能多重繼承),A 類別只能自己實作 C 介面,某種程度上算是一種 duplicated code。

自己在設計 Comic Surfer 的過程中,為了讓既有的外掛和主程式能維持 binary compatibility,也採用上述的慣例,在公開的外掛 SDK 裡,同時放了介面和抽象類別,不過在開發文件上鼓勵開發者以繼承抽象類別的方式開發外掛,這樣的設計是當我想在介面中新增函式的宣告,同時在抽象類別中為新增的函式提供預設的實作,如此,即使外掛沒有用新的 SDK 進行修改,還是能在新版的 Comic Surfer 中掛載使用,而不會讓 JVM 抱怨說某個類別沒有提供完整的實作 (不相容)。

上述的問題,在這次的 default methods 得到了一個不錯的解法。首先,工具函式不需要再放在類別中了,如下所示,靜態函式的實作能直接放在介面中。


接著,抽象類別某種程度上可以被取代 (如果抽象類別帶有成員變數就沒辦法),假設要替溫度感測器設計一個介面,可以像 TemperatureSensor 介面,並提供二個 default methods:getTemperatureInCelsius()getTemperatureInFahrenheit()分別將絕對溫度轉成常用的攝氏溫度和華氏溫度 (這個例子是一個簡化的示意。不算是一個好設計,因為這轉換應該算是能抽離到外部變成一個獨立類別)。這時過去需要提供類似 AbstractTemperatureSensor 的抽象類別就完全不需要了,因為實作已經放在介面裡了。


不需要抽象類別,提供了一個設計上的彈性,例如要替一個溫度感測裝置控制器 (AbstractDeviceControl) 寫 Adapter時,在沒有 default methods 前,因為單一繼承,要思考是讓 Adapter 繼承 AbstractDeviceControl 比較好,還是繼承 AbstractTemperatureSensor 比較好?當然,用 composition 解決更好,只是有時候會多寫些程式。現在可以像 TemperatureSensorDevice 用透過實作介面的方式取得預設實作,然後將唯一的繼承機會讓給其他可能需要的設計。


然後,如果要替介面新增函式,也可以透過直接在介面中提供預設實作,提供向下相容性。例如新增蘭金溫度表示法 (Rankine scale) 到 TemperatureSensor 中,並提供預設的實作,這時 TemperatureSensorDevice 不需做任何修改就能夠使用。


既然是介面,當然能用擴充 (extend) 的方式產生新介面,例如,某些國家習慣用華氏溫度,所以很多感測器都只提供華氏溫度,這時可以擴充 TemperatureSensor 介面產生新介面 FahrenheitTemperatureSensor,在擴充的時候,可以用 abstract 關鍵字將一個有預設實作的 getTemperatureInFahrenheit() 重新宣告為抽象函式,或是複寫既有的實作,例如複寫 getTemperatureInCelsius() 變成直接取華氏溫度轉攝氏溫度,不用多轉一次絕對溫度然後再轉攝氏溫度。


其實,default methods 似乎也引起不小的討論,因為 default methods 加上可以實作多個介面,已經有點像 C++ 的多重繼承了,只差在沒辦法繼承成員變數而已,是好是壞就看怎麼使用了。我個人覺得還蠻方便的,如果不是因為 default methods 要到 Java 8 才有,我倒是考慮將 Comic Surfer 的外掛介面和工具函式類別用 default methods 改寫。

avatar-img
Spirit的沙龍
53會員
104內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
留言
avatar-img
留言分享你的想法!
Spirit的沙龍 的其他內容
Lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。
老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
內容十分精實,一百多頁很薄的一本書,但含了很多有用的資訊,就算不是開發微服務,書中的內容也可以用在很多雲端服務的開發與維運上。中文版唯一可惜的地方,翻譯非常不通順,很多不像中文的句子,會看到好幾個「與」連在一起用,標點符號的用法也有點怪,閱讀的痛苦指數有點高...
3/5高品質微服務
今天的任務是找到獨一無案的方法創造新事物,不只讓未來變得不一樣,而且要更好,所以我們要從 0 到 1。最重要的第一步是自己獨立思考。唯有重新看待世界,像古人首次見到它那樣覺得新鮮古怪,我們才能重新創造,並將更好的未來留給後世。
5/5從 0 到 1
Lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。
老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
內容十分精實,一百多頁很薄的一本書,但含了很多有用的資訊,就算不是開發微服務,書中的內容也可以用在很多雲端服務的開發與維運上。中文版唯一可惜的地方,翻譯非常不通順,很多不像中文的句子,會看到好幾個「與」連在一起用,標點符號的用法也有點怪,閱讀的痛苦指數有點高...
3/5高品質微服務
今天的任務是找到獨一無案的方法創造新事物,不只讓未來變得不一樣,而且要更好,所以我們要從 0 到 1。最重要的第一步是自己獨立思考。唯有重新看待世界,像古人首次見到它那樣覺得新鮮古怪,我們才能重新創造,並將更好的未來留給後世。
5/5從 0 到 1