top

【Guava 教學】(10)進行函數式程式設計

【Guava 教學】(9)ListenableFuture 聽取未來需求 << 前情

由於 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 的 Predicateapply 並不會被執行。Java 開發者的函數式程式設計系列 中提到的 map 函式,在 Guava 中對應的是 transform 方法,例如取得清單所有名稱之長度清單,可以如下:

Iterable lengthes = transform(names, name -> name.length());

Guava 不提供 reduce 這類功能的函式,這應該是基於可讀性的關係,因為 reduce 這個名稱(或像是 foldfoldLeftfoldRight 等)本身並不容易讓人瞭解它的意涵,不過對於一些可用 reduce 做到的功能,但有更明確目的且常用的函式,Guava 則提到有 allany 等函式。

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) 提到的,filtertransform 那些函式,比較像是二級公民,感覺不符合 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"));

曾經聽其他人說過,看不懂 IterablesIterators 上那些方法怎麼使用,我想可能的原因有兩個,一是因為 JDK8 前沒有 Lambda,只能使用醜醜的匿名類別,不知道用這些方法有什麼好處,二是不清楚函數式風格與由來,不知道怎麼搭配那些 API 來取得想要的結果,如果你瞭解了函數式設計,也擁有了 Lambda,那麼應該就知道怎麼善用它們了吧!看看本站的兩個系列:

加上這篇文章,相信再去看 Guava 的 FunctionalExplained,就可以知道它上頭在說些什麼了。

 

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言