Guava函數式編程

函數式編程

截至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構造和操作方法,包括:

方法 - -
forMap(Map<A, B>) compose(Function<B, C>, Function<A, B>) constant(T)
identity() toStringFunction()

細節請參考Javadoc。

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

方法 - -
instanceOf(Class) assignableFrom(Class) contains(Pattern)
in(Collection) isNull() alwaysFalse()
alwaysTrue() equalTo(Object) compose(Predicate, Function)
and(Predicate...) or(Predicate...) not(Predicate)

細節請參考Javadoc。

使用函數式編程

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

斷言

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

集合類型 過濾方法
Iterable Iterables.filter(Iterable, Predicate)FluentIterable.filter(Predicate)
Iterator Iterators.filter(Iterator, Predicate)
Collection Collections2.filter(Collection, Predicate)
Set Sets.filter(Set, Predicate)
SortedSet Sets.filter(SortedSet, Predicate)
Map Maps.filterKeys(Map, Predicate)Maps.filterValues(Map, Predicate)Maps.filterEntries(Map, Predicate)
SortedMap Maps.filterKeys(SortedMap, Predicate)Maps.filterValues(SortedMap, Predicate)Maps.filterEntries(SortedMap, Predicate)
Multimap Multimaps.filterKeys(Multimap, Predicate)Multimaps.filterValues(Multimap, Predicate)Multimaps.filterEntries(Multimap, Predicate)

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

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

Iterables****方法簽名 說明 另請參見
boolean all(Iterable, Predicate) 是否所有元素滿足斷言?懶實現:如果發現有元素不滿足,不會繼續迭代 Iterators.all(Iterator, Predicate)FluentIterable.allMatch(Predicate)
boolean any(Iterable, Predicate) 是否有任意元素滿足元素滿足斷言?懶實現:只會迭代到發現滿足的元素 Iterators.any(Iterator, Predicate)FluentIterable.anyMatch(Predicate)
T find(Iterable, Predicate) 循環並返回一個滿足元素滿足斷言的元素,如果沒有則拋出NoSuchElementException Iterators.find(Iterator, Predicate)Iterables.find(Iterable, Predicate, T default) Iterators.find(Iterator, Predicate, T default)
Optional<T> tryFind(Iterable, Predicate) 返回一個滿足元素滿足斷言的元素,若沒有則返回Optional.absent() Iterators.find(Iterator, Predicate) Iterables.find(Iterable, Predicate, T default) Iterators.find(Iterator, Predicate, T default)
indexOf(Iterable, Predicate) 返回第一個滿足元素滿足斷言的元素索引值,若沒有返回-1 Iterators.indexOf(Iterator, Predicate)
removeIf(Iterable, Predicate) 移除所有滿足元素滿足斷言的元素,實際調用Iterator.remove()方法 Iterators.removeIf(Iterator, Predicate)

函數

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

集合類型 轉換****方法
Iterable Iterables.transform(Iterable, Function)FluentIterable.transform(Function)
Iterator Iterators.transform(Iterator, Function)
Collection Collections2.transform(Collection, Function)
List Lists.transform(List, Function)
Map* Maps.transformValues(Map, Function)Maps.transformEntries(Map, EntryTransformer)
SortedMap* Maps.transformValues(SortedMap, Function)Maps.transformEntries(SortedMap, EntryTransformer)
Multimap* Multimaps.transformValues(Multimap, Function)Multimaps.transformEntries(Multimap, EntryTransformer)
ListMultimap* Multimaps.transformValues(ListMultimap, Function)Multimaps.transformEntries(ListMultimap, EntryTransformer)
Table Tables.transformValues(Table, Function)

*Map和Multimap有特殊的方法,其中有個EntryTransformer<K, V1, V2>參數,它可以使用舊的鍵值來計算,並且用計算結果替換舊值。

對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使用的類包括:

方法 -
Ordering Ordering.onResultOf(Function)
Predicate Predicates.compose(Predicate, Function)
Equivalence Equivalence.onResultOf(Function)
Supplier Suppliers.compose(Function, Supplier)
Function Functions.compose(Function, Function)

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

方法
Futures.transform(ListenableFuture, Function)
Futures.transform(ListenableFuture, Function, Executor)
Futures.transform(ListenableFuture, AsyncFunction)
Futures.transform(ListenableFuture, AsyncFunction, Executor)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章