1. 程式人生 > >[Google Guava] 4-函數語言程式設計

[Google Guava] 4-函數語言程式設計

原文連結 譯文連結 譯者:沈義揚,校對:丁一

注意事項

截至JDK7,Java中也只能通過笨拙冗長的匿名類來達到近似函數語言程式設計的效果。預計JDK8中會有所改變,但Guava現在就想給JDK5以上使用者提供這類支援。

過度使用Guava函數語言程式設計會導致冗長、混亂、可讀性差而且低效的程式碼。這是迄今為止最容易(也是最經常)被濫用的部分,如果你想通過函式式風格達成一行程式碼,致使這行程式碼長到荒唐,Guava團隊會淚流滿面。

比較如下程式碼:

Function<String, Integer> lengthFunction = new Function<String, Integer>() {
    public Integer apply(String string) {
        return string.length();
    }
};
Predicate<String> allCaps = new Predicate<String>() {
    public boolean apply(String string) {
        return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string);
    }
};
Multiset<Integer> lengths = HashMultiset.create(
     Iterables.transform(Iterables.filter(strings, allCaps), lengthFunction));

或FluentIterable的版本

Multiset<Integer> lengths = HashMultiset.create(
    FluentIterable.from(strings)
        .filter(new Predicate<String>() {
            public boolean apply(String string) {
                return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string);
            }
        })
        .transform(new Function<String, Integer>() {
            public Integer apply(String string) {
                return string.length();
            }
        }));

還有

Multiset<Integer> lengths = HashMultiset.create();
for (String string : strings) {
    if (CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string)) {
        lengths.add(string.length());
    }
}

即使用了靜態匯入,甚至把Function和Predicate的宣告放到別的檔案,第一種程式碼實現仍然不簡潔,可讀性差並且效率較低。

截至JDK7,命令式程式碼仍應是預設和第一選擇。不應該隨便使用函式式風格,除非你絕對確定以下兩點之一:

  • 使用函式式風格以後,整個工程的程式碼行會淨減少。在上面的例子中,函式式版本用了11行, 命令式程式碼用了6行,把函式的定義放到另一個檔案或常量中,並不能幫助減少總程式碼行。
  • 為了提高效率,轉換集合的結果需要懶檢視,而不是明確計算過的集合。此外,確保你已經閱讀和重讀了Effective Java的第55條,並且除了閱讀本章後面的說明,你還真正做了效能測試並且有測試資料來證明函式式版本更快。

請務必確保,當使用Guava函式式的時候,用傳統的命令式做同樣的事情不會更具可讀性。嘗試把程式碼寫下來,看看它是不是真的那麼糟糕?會不會比你想嘗試的極其笨拙的函式式 更具可讀性。

Functions[函式]和Predicates[斷言]

本節只討論直接與Function和Predicate打交道的Guava功能。一些其他工具類也和”函式式風格”相關,例如Iterables.concat(Iterable<Iterable>),和其他用常量時間返回檢視的方法。嘗試看看2.3節的集合工具類

Guava提供兩個基本的函式式介面:

  • Function<A, B>,它聲明瞭單個方法B apply(A input)。Function物件通常被預期為引用透明的——沒有副作用——並且引用透明性中的”相等”語義與equals一致,如a.equals(b)意味著function.apply(a).equals(function.apply(b))。
  • Predicate<T>,它聲明瞭單個方法boolean apply(T input)。Predicate物件通常也被預期為無副作用函式,並且”相等”語義與equals一致。

特殊的斷言

字元型別有自己特定版本的Predicate——CharMatcher,它通常更高效,並且在某些需求方面更有用。CharMatcher實現了Predicate<Character>,可以當作Predicate一樣使用,要把Predicate轉成CharMatcher,可以使用CharMatcher.forPredicate。更多細節請參考第6章-字串處理。

此外,對可比較型別和基於比較邏輯的Predicate,Range類可以滿足大多數需求——它表示一個不可變區間。Range類實現了Predicate,用以判斷值是否在區間內。例如,Range.atMost(2)就是個完全合法的Predicate<Integer>。更多使用Range的細節請參照第8章。

操作Functions和Predicates

Functions提供簡便的Function構造和操作方法,包括:

細節請參考Javadoc。

相應地,Predicates提供了更多構造和處理Predicate的方法,下面是一些例子:

細節請參考Javadoc。

使用函數語言程式設計

Guava提供了很多工具方法,以便用Function或Predicate操作集合。這些方法通常可以在集合工具類找到,如Iterables,Lists,Sets,Maps,Multimaps等。

斷言

斷言的最基本應用就是過濾集合。所有Guava過濾方法都返回”檢視”——譯者注:即並非用一個新的集合表示過濾,而只是基於原集合的檢視

*List的過濾檢視被省略了,因為不能有效地支援類似get(int)的操作。請改用Lists.newArrayList(Collections2.filter(list, predicate))做拷貝過濾。

除了簡單過濾,Guava另外提供了若干用Predicate處理Iterable的工具——通常在Iterables工具類中,或者是FluentIterable的”fluent”(鏈式呼叫)方法。

函式

到目前為止,函式最常見的用途為轉換集合。同樣,所有的Guava轉換方法也返回原集合的檢視。

*對Set的轉換操作被省略了,因為不能有效支援contains(Object)操作——譯者注:懶檢視實際上不會全部計算轉換後的Set元素,因此不能高效地支援contains(Object)請改用Sets.newHashSet(Collections2.transform(set, function))進行拷貝轉換。

List<String> names;
Map<String, Person> personWithName;
List<Person> people = Lists.transform(names, Functions.forMap(personWithName));

ListMultimap<String, String> firstNameToLastNames;
// maps first names to all last names of people with that first name

ListMultimap<String, String> firstNameToName = Multimaps.transformEntries(firstNameToLastNames,
    new EntryTransformer<String, String, String> () {
        public String transformEntry(String firstName, String lastName) {
            return firstName + " " + lastName;
        }
    });

可以組合Function使用的類包括:

此外,ListenableFuture API支援轉換ListenableFuture。Futures也提供了接受AsyncFunction引數的方法。AsyncFunction是Function的變種,它允許非同步計算值。