此為過去的舊文,2014 年 3 月 20 日初次發表於 logdown。
Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
Java 8 最亮眼的特色之一應該就是所謂的 Lambda 表示法,Lambda 表示法幾乎內建在很多語言中,而我用最多的應該在 JavaScript 和 Objective C (code block) 中了。但老實說,我對於 Lambda 其實不怎麼有愛,JavaScript 版的寫法我覺得還好,但 Objective C 的 in place code block 我看了覺得好亂,後來大多數的情況下,我都用 method 回傳 code block 的方式在使用。
既然,Java 8 開始支援 Lambda 表示法,那程式的撰寫上會變成怎樣呢?首先,以排序為例,先回到沒有 Lambda 的 Java 世界,要排序一個 List
,要先寫一個客製的 Comparator
(下方的 IntegerComparator
),然後再建立一個 comparator
物件作為 List.sort()
函式的參數 (LambdaExample
中的 sortWithComparator
函式)。
就這點,很多人覺得 Java 很沒有生產力 (Productivity),不過我個人是還蠻喜歡這種寫法,我喜歡寫很多小而簡單的 class,因為用 JUnit 測試這些 class 也很容易 (小 class 邏輯相對簡單易測),另一個好處是越是小的 class 越是容易重複利用。
如果不想另外寫一個 class,在沒有 Lambda 的 Java 世界中其實還有一種寫法,也就是匿名 class (anonymous class),LambdaExample
中的 sortWithAnonymousClass
函式就是這種寫法,這種寫法讓我想起早期剛開始學 Java 時用 JBuilder 這類的 IDE 拖拉 UI,然後在按鈕上點兩下,IDE 會自動產生一段程式碼,然後引導你到某個位置開始寫程式(嗯...Visual Basic 6 好像也是這樣),IDE 產生出來的程式大多是這類的匿名 class。在學了比較正式的物件導向設計後,我幾乎不再用這種寫法,只有偶而在比較沒有影響整體設計的情況下,會偷懶使用一下。不過這種寫法還是有人覺得沒生產力。
Lambda 出現後,程式變成 LambdaExample
中 sortWithLambdaExpression
函式所示那樣,確實行數減少不少,也有人認為可讀性提高不少,認為可讀性提高的原因是,程式碼就在那裡,不像過去還需要跳到另外一個 class 才能看到實作。關於可讀性這一點,我覺得只有在 Lambda 內的程式邏輯很簡單才成立,如果邏輯很複雜,把兩個不同邏輯的程式碼放在一起,不僅長度變長,可讀性反而降低,與其這樣還不如抽出來成為一個 class 並給予一個有意義的 class 名稱。至於語法用 (arguments) -> {implementation} 表示,可能是 Objective C 的 code block 看多了,有比剛開始第一次看到 Java Lambda 表示法稍微習慣多了。
Java 8除了這種匿名的 Lambda 表示法,其實也是支援將既有函式當成 Lambda 使用的用法,例如 LambdaExample
中 sortWithMethodReference
和 sortWithStaticMethodReference
。不知道大家覺得 sortWithLambdaExpression
和 sortWithMethodReference
兩相比較下,哪個可讀性較高呢?我個人是覺得 sortWithMethodReference
比較高,因為從函式的名稱就可以知道是以升冪的方式排序。我個人在寫 JavaScript 時也是比較喜歡這種寫法。
有人可能會提到 Closure 才是最主要的精神,關於這點,我想留到下一篇在討論,這篇先討論 Lambda 對於可讀性和生產力的影響。就可讀性來說,剛剛已經提過了,如果是簡單的邏輯用 Lambda 包起來,確實有提高一部分的可讀性,但如果邏輯很複雜,混雜在一起我個人是覺得反而降低可讀性。就生產力來說,Lambda 某種程度上可以少寫一些程式碼(組成類別的宣告等最低成本),和剛剛相同,如果邏輯簡單,例如 IntegerComparator
中 compare
函式只有一行,那為了這一行所付出的成本是很高的,但如果邏輯複雜,少寫最低成本所產生的生產力提升效益就有限了。
而且生產力不能只看少寫多少行程式,以長遠來看,好測試的程式碼反而能帶來更高的生產力,這一點也是我最近開發 iOS App 時大量使用 method 回傳 code block 的原因,我認為提高可測性所產生的生產力提升效益比直接少寫程式碼來的高很多,畢竟維護程式碼的時間遠比撰寫程式碼的時間來的多很多。看下面的範例,doSomething
可能是一個整合的函式,其中夾雜了一個 Lambda 表示法的邏輯在裡面,為了測這段 Lambda 表示法的邏輯,必須透過測試 doSomething
來完成,但如果是一個單獨的 class 如 IntegerComparator
這樣,可以不用透過測 doSomething
來完成,而是直測 IntegerComparator
。或是用 method reference 的方式也很好測。
有人可能覺得這例子不太好,確實要提高可測性的方法還有很多,不過,我認為匿名 Lambda 表示法的可測性是很低的。所以如果真的要使用 Java 8 的 Lambda 表示法,我應該會傾向使用 method reference 的方式,除了可測性外,另一個原因是, method reference 可以根據 OO 的準則,例如 single responsibility principle,將合適的函式放在合適的 class中,然後用 method reference 的方式直接引用作為 Lambda 來使用。如此一來,既可提高可讀性 (函式名稱本身可以提高可讀性)、可維護性 (好測試,好的 OO 設計),也提高了可重複利用的程度 (匿名Lambda無法重複利用,但 method reference 可以)。