自Java 8發表的這一年多以來,無論是Lambda語法或者是Stream API等,Java 8的接受度日益得到好評,不過,多數已在運行的平臺仍停留在Java 7或更之前的版本,如果想提前享用Java 8的益處,那麼採用retrolambda會是個不錯選擇,也會是重構程式碼,為升級Java 8提前做準備的好機會。

retrolambda是什麼?

雖然Java 8的Lambda語法等功能,對Java開發者意義重大,然而無論是人力面或是技術面,不少單位為了不破壞既有系統的和諧,升級Java 8依舊不是選項之一,例如2015年4月初Java 7公開發行版本EOL(End of Life)之後,有國外鄉民就打趣地說:「Luckily we're still on Java 6. Java 7 EOL: no problem.」,不過,如果只是想在語法層面提前享用Lambda語法,可考慮採用retrolambda來導入。

簡單來說,retrolambda可以讓Java開發者使用Java 8的Lambda表示式、方法參考、介面上預設方法與靜態方法等語法,以及Java 7的try-with-resources語法撰寫程式,並讓程式可運行於Java 7、6、5的平臺上;在技術面上,retrolambda改寫Java 8編譯出來的位元碼(bytecode),將Lambda表示式與方法參考(Method reference)轉換為Java 7,或先前平臺前可以看得懂的匿名類別,如果是Java 7前的平臺,try-with-resources產生的Throwable.addSuppressed呼叫,會被移除。

因此,開發環境還是使用Java 8撰寫與編譯程式,之後透過retrolambda進行位元碼改寫,過程可以透過建構自動化,retrolambda官方Github的repository介紹中,提供了Maven plugin與Gradle plugin的使用方式,而在最基本命令列操作上,retrolambda提供了retrolambda.jar,因此開發者若想用老牌建構工具Ant,也能自行撰寫target來完成任務(可參考〈lambda support for android〉的內容)。

retrolambda提供語法的向後移值,但不提供Java 8標準API的向後移植;retrolambda對介面上的預設方法(Default method)與靜態方法,提供有限的支援,預設方法會被改寫為companion類別(介面名稱$XXXX的類別)中的抽象方法實作,而靜態方法則在改寫時,被移至companion類別,由於一些技術上的限制,介面上的預設方法與靜態方法的支援,預設會是不啟用狀態,這部份主要是為了Java 8 API的改進,對於Lambda語法本身的採用,影響不大。

進一步導入前的重構與測試

就工具來說,retrolambda使用很簡單,導入的難點在於,既有程式碼如何能善用Lambda等功能。

最顯而易見的第一步,當然是將既有的匿名類別,試著改為 Lambda表示式,程式碼中首先直接受惠的是視窗程式中的事件處理,以及大量使用非同步操作的Android程式碼,對後者的改進尤其重要,因為Android開發平臺目前仍不支援Java 8語法,因此當程式碼中充滿著大量的非同步操作,就會隨之產生大量的匿名類別與回呼地獄(Callback hell),令程式碼可讀性迅速下降。因此,目前retrolambda在Android開發者之間,擁有比較高的討論度。

除了改善匿名類別的可讀性問題之外,也可以試著使用retrolambda,來做為重構程式碼的工具。就像多觀察匿名類別改為Lambda表示式之後,可否進一步運用方法參考,以利用既有的方法,而不是到處充斥Lambda表示式。例如,思考我先前專欄〈Java 8的函式重用〉中,談到的各種重用之可能性,或者是如〈瞄準Lambda改造〉中談到的,從Code as data角度出發,設法嗅出過去未察覺,或只得忍受的程式餿味,加以重構。

除了語法層面之外,另一個可進行重構的重要部份,是Java 8搭配Lambda語法的Stream等API帶來的新開發風格。對於習慣舊式風格的Java開發者來說,這會是比較困難的部份,我曾經在〈務實的函數式設計〉探討過,這實際上需要的重構觀念與技巧,不用多,前置步驟,往往是讓方法中的程式碼行數儘量地少,儘量地只做一件事,然後,再進一步運用Stream API等相關元素,做更徹底地重構。

這個過程可以拿《重構--改善既有程式的設計》第一章的影片出租店範例,來作為嘗試。為了在重構過程不破壞任何既有功能,開發者得先為程式碼建立測試。由於《重構》撰寫時並沒有Java 8相關元素,第二章重構完成後,若進一步地Java 8觀點察看,其實還可使用Lambda與Stream API重構,這個過程可以在我的〈Java 8與retrolambda〉演講投影片中看到,結合我Github上的Commit history(https://goo.gl/43Bsdx),可以更清楚觀察如何做更進一步重構。

導入向後移植API

由於retrolambda並不提供Java 8標準API的向後移植,如果目標是將編譯、改寫後的位元碼,放在Java 7、6、5的平臺上運行,重構過程就不能用到目標平臺不提供的API。由於Java 8中一些新API,吸收不少既有的開放原始碼程式庫優點,因此,這些程式庫,會替代API是可考慮採用的對象。

例如,在〈Java 8 Friday: Let’s Deprecate Those Legacy Libs〉中,作者就特別列出了一些平臺移植至Java 8之後,可棄用的一些程式庫,但如果你想導入retrolambda,有些反而是可考慮採用的程式庫,像是guava-libraries中FluentIterable等部份的API,最主要的考量點,是程式庫是否對Lambda語法友善,也就是API上是否以函式介面(Functional interface)作為Lambda目標型態。

如果你想取得與Java 8 Stream 更相近的API協定,目前,retrolambda官方已經列出了streamsupport程式庫。而且,在這個程式庫當中,連平行Stream、CompletableFuture,以及一些如Optional、StringJoiner等,都已經移植,同時,也支援java.util.concurrent中那些針對Java 7、8的改進API,令它們能運行於Java 6、5之上。

如果對於Java 8的新日期時間API很有興趣,方案之一,是使用Joda-Time,或者是使用retrolambda官方列出的ThreeTen,作為替代API。ThreeTen是新日期與時間API,正式進入Java 8前的開放原始碼專案,因此與Java 8中的API協定有著極高的相似性。

retrolambda作為重構工具

當開發者逐漸接受Java 8的同時,往往也能體認到Lambda在匿名類別撰寫的簡化上只是一小部份。

Java 8最重要的,是帶來程式撰寫風格的改變,而對既有的程式碼來說,必須經過一般重構,才得以享受到改變後帶來的好處,就算只想將Date與Calendar改為Java 8新日期時間API,也是得經過一些重構,才得以享有。而且,既有程式碼在套用Stream API的過程,也會是一連串重構的過程。

在非同步處理上,也是類似。就算有了Lambda,一些API設計也是會產生回呼地獄的問題,由於streamsupport也有CompletableFuture,因此也就有機會,將一些使用了ExecutorService的程式碼重構,改成Promise風格,讓程式碼可以進行傳遞與組合。

如果有更複雜的非同步處理需求,可以進一步考慮使用RxJava。不過,RxJava是更龐大的程式庫,並且融合了Functional與Reactive典範,這表示開發者得對程式碼做更進一步的重構,才能確認本身的需求是否適合使用RxJava來解決,在〈NotRxJava guide for lazy folks〉中,就在逐步建構出非同步需求下,產生了種種可讀性問題,而後以重構為推進力,逐步將程式碼回復至如同未採用非同步前的簡明,也逐步瞭解了應用程式的非同步需求。

單看retrolambda本身的使用,其實沒有太多可著墨的地方,重點在可透過retrolambda,令Java 8 Lambda導入後的重構過程,提前在仍在運行Java 7、6、5的平臺上進行,這才是使用retrolambda最重要的意義與動機。將來,若平臺真能升級至Java 8,只要重新編譯程式碼就可直接運行,也會是實質的效益之一。

專欄作者

熱門新聞

Advertisement