由於 JDK8 Lambda 的引進,未來 JDK8 將有進行函數式程式設計的可能性,剛好本站 Java 開發者的函數式程式設計系列 已經告一段落,閱讀該系列的文章,將有助於將來在 JDK8 中,善用那些從函數式程式設計借鏡而來的相關設計,無論是 Lambda 語法或程式庫的使用。
那麼在 JDK7 前的版本呢?如果想在 Java 中進行函數式風格的設計有可能嗎?其實有一些第三方程式庫提供了這類風格的封裝,其中包括了 Guava 程式庫,然而缺少 Lambda 語法的輔助,使用 Guava 程式庫的相關 API 並不一定會因為函數式風格而受益,正如 Guava 的 FunctionalExplained 中提到的,除非使用這些 API 進行函數式風格設計時,對可讀性有所幫助,或者是取得了 惰性 處理上的一些益處,不然命令式仍應是 Java 的風格選擇。
雖然 Guava 的 FunctionalExplained 中也提到,其函數式風格的 API 主要是針對 JDK5 到 JDK7 的使用者,不過使用 JDK8 的 Lambda 語法來搭配 Guava 的函數式風格 API,似乎也是不錯的選擇,特別是你要與 Guava 的其他 API 做溝通,或甚至你覺得 JDK8 的 API 設計得有些醜陋時。
Guava 的
Iterables
與 Iterators
提到了一些函數式風格的函式,呃!Java 中是叫做靜態方法啦!總之,Java 中的靜態方法,某些程度上就只是將類別名稱當做名稱空間,就稱它們是函式吧!Iterables
上的函式針對有 Iterable
行為的物件,而 Iterators
是針對實作 Iterator
介面的物件,如此而已,因而如果你有個 List<String>
的名稱清單,想要過濾出長度小於 5 的名稱,可以在 import static com.google.common.collect.Iterables.*
後,直接進行如下的風格撰寫:
Iterable<String> filteredNames = filter(names, name -> name.length() < 5);
看到傳回值是 Iterable
型態,你應該要意識到這可能實現了惰性,在實際迭代傳回的物件前,傳給 filter
的 Predicate 之 apply
並不會被執行。Java 開發者的函數式程式設計系列 中提到的 map
函式,在 Guava 中對應的是 transform
方法,例如取得清單所有名稱之長度清單,可以如下:
Iterable lengthes = transform(names, name -> name.length());
Guava 不提供 reduce
這類功能的函式,這應該是基於可讀性的關係,因為 reduce
這個名稱(或像是 fold
、foldLeft
、foldRight
等)本身並不容易讓人瞭解它的意涵,不過對於一些可用 reduce
做到的功能,但有更明確目的且常用的函式,Guava 則提到有 all
、any
等函式。Predicates
上也提到了一些函式,例如,若你有兩個 Predicates
,像是 name -> name.length() < 5
而且 name -> name.startWith("Java")
,就可以用 Predicates
上的 and
來組合。例如:
Iterable<String> filteredNames = filter(
names,
and(name -> name.length() < 5, name -> name.startsWith("Java"))
);
那麼,如果想進行鏈狀操作呢?畢竟上面的寫法,比較像是 Java 開發者的函數式程式設計(5) 提到的,filter
、transform
那些函式,比較像是二級公民,感覺不符合 Java 物件導向的主流典範,如果想過濾出清單中名稱長度小於 5,接著取得那些名稱的大寫,然後看看有沒有任何一個名稱是包括 "JAVA" 的,用上面的寫法會變成:
boolean anyJAVA = any(transform(filter(names, name -> name.length() < 5), name -> name.toUpperCase()), name -> name.equals("JAVA"));
幾乎沒什麼可讀性,你可以改用 FluentIterable
來進行操作,FluentIterable
上有個 from
方法,可以直接將你的 Iterable
包裹,傳回 FluentIterable
實例,接著你就可以進行以下風格的操作:
boolean anyJAVA = FluentIterable.from(names)
.filter(name -> name.length() < 5)
.transform(name -> name.toUpperCase())
.anyMatch(name -> name.equals("JAVA"));
曾經聽其他人說過,看不懂 Iterables
、Iterators
上那些方法怎麼使用,我想可能的原因有兩個,一是因為 JDK8 前沒有 Lambda,只能使用醜醜的匿名類別,不知道用這些方法有什麼好處,二是不清楚函數式風格與由來,不知道怎麼搭配那些 API 來取得想要的結果,如果你瞭解了函數式設計,也擁有了 Lambda,那麼應該就知道怎麼善用它們了吧!看看本站的兩個系列:
加上這篇文章,相信再去看 Guava 的 FunctionalExplained,就可以知道它上頭在說些什麼了。