使用Guava進行函數式編程

本文翻譯自Getting Started with Google Guava這本書,若有翻譯不足的地方請指出。

在這一章,咱們開始注意到使用Guava進行編寫代碼會更加簡單。咱們將看看如何使用Guava當中的接口和類能夠幫助咱們,經過應用行之有效的模式,以使咱們的代碼更容易維護以及健壯。 java

在本章中咱們將包含一下幾點:
  • Function接口:這說明在java編程當中能夠引入函數式編程。同時也說明了如何使用Function接口以及最好的使用方式。
  • Functions類:Functions類包含一些實用的方法來操做Fucntion接口的實例。
  • Predicate接口:這個接口是評估一個對象是否知足必定條件,若是知足則返回true。
  • Predicates類:這個類是對於Predicate接口的指南類,它實現了Predicate接口而且很是實用的靜態方法。
  • Supplier接口:這個接口能夠提供一個對象經過給定的類型。咱們也能夠看到經過各類各樣的方式來建立對象。
  • Suppliers類:這個類是Suppliers接口的默認實現類。
使用Function接口
函數式編程強調使用函數,以實現其目標與不斷變化的狀態。這與大多數開發者熟悉的改變狀態的編程方式造成對比。Function接口讓咱們在java代碼當中引入函數式編程成爲可能。
Function接口當中只有2個方法:
public interface Function<F,T> {
  T apply(F input);
  boolean equals(Object object);
}
咱們不會具體的使用equals方法來判斷A對象與B對象是否相等,只會調用apply方法來比較A對象與B對象是否相等。apply方法接受一個參數而且返回一個對象。一個好的功能實現應該沒有反作用,這意味着當一個對象傳入到apply方法當中後應該是保持不變的。下面是一個接受Date對象而且返回Date格式化後字符串的例子:
public class DateFormatFunction implements Function<Date,String> {
  @Override
  public String apply(Date input) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
    return dateFormat.format(input);
  }
}
在這個例子當中,咱們能夠清楚看到Date對象正在經過SimpleDateFormat類轉換成咱們指望格式的字符串。雖然這個例子可能過於簡單,可是它演示了Function接口的做用,轉換一個對象而且隱藏了實現的細節。經過這個例子咱們可使用實現了Function接口的類,咱們也可使用匿名類來實現。看看下面的例子:
Function<Date,String> function = new Function<Date, String>() {
  @Override
  public String apply( Date input) {
    return new SimpleDateFormat("dd/mm/yyyy").format(input); 
  }
};
這2個例子沒什麼不一樣。一個是簡單的實現了Function接口,另外一個是匿名類。實現了Function接口的類的優勢是,你可使用依賴注入來傳遞一個函數接口到一個協做的類中,使得代碼高內聚。
 
使用Function接口的參考
這是一個很好的機會討論在你的代碼中使用匿名類來引入Function接口。在java當前的狀態中,咱們沒有閉包特性。當JAVA8發佈後將會改變,如今java的回答就是使用匿名類。當你使用匿名類來充當閉包的時候,語法是至關的繁重。若是使用太頻繁,會使你的代碼很難跟蹤和維護。事實上,對上面的例子分析,這個例子只是爲了演示Function是如何使用,咱們不能獲取到更多的好處。例如,下面代碼來實現這個功能會更好。
public String formatDate(Date input) {
  return new SimpleDateFormat("dd/mm/yyyy").format(input);
}
如今比較一下這兩個例子,咱們會發現後面的更加容易讀懂。當咱們使用Function取決於你想在什麼地方進行轉換。若是你有一個類裏面包含一個Date實例變量和一個返回日期轉換成指望字符串的方法,你可能更好的執行後面的例子。然而,若是你有一個Date對象的集合而且想獲取這些Date的字符串形式的list,使用Function接口多是一個不錯的方法。這裏的要點是,你不能由於能夠就放棄使用Function匿名實例在你的代碼裏。看你的代碼,你是否從函數編程中得到了好處。第四章Guava Collections和第六章Guava Cache咱們會看些實用的例子。
 
使用Functions類
Functions類包含一些實用的方法來操做Fucntion接口的實例。在本節當中,咱們將會講其中的兩個方法。
 
使用Functions.forMap方法
forMap方法接受一個Map<K,V>的參數而且返回一個Function<K,V>實例,執行apply方法會在map當中進行查找。例如,考慮下面的類表明美國的一個州。
public class State {
  private String name;
  private String code;
  private Set<City> mainCities = new HashSet<City>(); 
  //省去getter和setter方法 }
如今你有一個名爲stateMap的Map<String, State>,他的key就是州名的縮寫。如今咱們能夠建立一個經過州代碼來查找的函數,你只須要下面幾步:
Function<String,State> lookup = Functions.forMap(stateMap);
//Would return State object for NewYork
lookup.apply("NY");
使用Functions.forMap方法有一個警告。若是傳入的key在map當中不存在會拋出IllegalArgumentException異常。然而,有一個重載的forMap方法增長一個默認值參數,若是key沒找到會返回默認值。經過使用Function接口來執行state的查找,你能夠很容易的改變這個實現。當咱們使用Splitter對象來建立一個map或者使用Guava collection包中其餘的一些方法來建立map,總之咱們能夠在咱們代碼當中借住Guava的力量。
 
使用Functions.compose方法
假設你如今有一個表明city的類,代碼以下:
public class City {
  private String name;
  private String zipCode;
  private int population;
//省去getter和setter方法   
public String toString() {     return name;   } }
考慮下面的情形:你要建立一個Function實例,傳入State對象返回當中mainCities逗號分割的字符串。代碼以下:
public class StateToCityString implements Function<State,String> {
  @Override
  public String apply(State input) {
    return Joiner.on(",").join(input.getMainCities());
  }
}
更進一步來講。你但願只有一個Function實例經過傳入State的名稱縮寫來返回這State當中mainCities逗號分割的字符串。Guava提升了一個很好的方法來解決這種狀況,Functions.compose方法接受兩個Function實例做爲參數,而且返回這兩個Function組合後的Function。因此咱們可使用上面的兩個Function來舉一個例子:
Function<String,State> lookup = Functions.forMap(stateMap);
Function<State, String> stateFunction = new StateToCityString();
Function<String,String> composed = Functions.compose(stateFunction ,lookup);
如今調用composed.apply("NY")方法將會返回:"Albany,Buffalo,NewYorkCity"
花一分鐘時間來看下方法的調用順序。composed接受一個「NY」參數而且調用lookup.apply()方法,從lookup.apply()方法中返回的值傳入了stateFunction.apply()方法當中而且返回執行結果。能夠理解爲第二個參數的輸入參數就是composed.apply方法的輸入參數,第一個參數的輸出就是composed.apply 方法的輸出。若是不使用composed 方法,在前面的例子將以下所示: 
String cities = stateFunction.apply(lookup.apply("NY"));
使用Predicate接口
Predicate接口與Function接口的功能類似。像Function接口同樣,Predicate接口也有2個方法:
public interface Predicate<T> {
  boolean apply(T input)
  boolean equals(Object object)
}
Function接口的狀況是,咱們不會去詳細的講解equals方法。apply方法會返回對輸入判定後的結果。在Fucntion接口使用的地方來轉換對象,Predicate接口則用於過濾對象。Predicates類的使用與Functions類同樣。當一個簡單方法能實現的不使用Predicates類。同時,Predicate接口沒有任何反作用。在下一章,會講到Collections,咱們會看到Predicate的最佳實踐。
 
Predicate接口的例子
這是Predicate接口的一個簡單的例子,咱們使用上面例子當中的City類。咱們定義一個Predicate來判斷這個城市是否有最小人口。
public class PopulationPredicate implements Predicate<City> {
  @Override
  public boolean apply(City input) {
    return input.getPopulation() <= 500000;
  }
}
在這個例子當中,咱們簡單的檢查了City對象當中的人口屬性,當人口屬性小於等於500000的時候會返回true。一般,你能夠將Predicate的實現定義成匿名類來過濾集合當中的每個元素。Predicate接口和Function接口是如此的類似,許多狀況下使用Fucntion接口的時候一樣可使用Predicate接口。
 
使用Predicates類
Predicates類是包含一些實用的方法來操做Predicate接口的實例。Predicates類提供了一些很是有用的方法,從布爾值條件中獲得指望的值,也可使用「and」和「or」方法來鏈接不一樣的Predicate實例做爲條件,而且若是提供「not」方法一個Predicate實例返回的值是false則「not」方法返回true,反之亦然。一樣也有Predicates.compose方法,可是他接受一個Predicate實例和一個Function實例,而且返回Predicate執行後的值,把Function執行後的值當中它的參數。讓咱們看些例子,咱們會更好的理解如何在代碼中使用Predicates類。先看個特殊的例子,假設咱們有下面兩個類的實例。
public class TemperateClimatePredicate implements Predicate<City> {
  @Override
  public boolean apply(City input) {
    return input.getClimate().equals(Climate.TEMPERATE);
  }
}
public class LowRainfallPredicate implements Predicate<City> {
  @Override
  public boolean apply(City input) {
    return input.getAverageRainfall() < 45.7;
  }
}
值得重申,一般咱們會使用匿名類,但爲清楚起見,咱們將使用具體類。 
 
使用Predicates.and方法
Predicates.and方法接受多個Predicate對象而且返回一個Predicate對象,所以調用返回的Predicate對象的apply方法當全部Predicate對象的apply方法都返回true的時候會返回true。若是其中一個Predicate對象返回false,其餘的Predicate對象的執行就會中止。例如,假如咱們只容許城市人口小於500,000而且年降雨量小於45.7英寸的。
Predicate smallAndDry = Predicates.and(smallPopulationPredicate, lowRainFallPredicate);
下面是Predicates.and方法的簽名:
Predicates.and(Iterable<Predicate<T>> predicates);
Predicates.and(Predicate<T> ...predicates);
使用Predicates.or方法
Predicates.or方法接受多個Predicate對象而且返回一個Predicate對象,若是當中有一個Predicate對象的apply方法返回true則總方法就返回true。
若是有一個Predicate實例返回true,就不會繼續執行。例如,假設咱們想包含城市人口小於等於500,000或者有溫帶氣候的城市。
Predicate smallTemperate = Predicates.or(smallPopulationPredicate, temperateClimatePredicate);
下面是Predicates.or方法的簽名:
Predicates.or(Iterable<Predicate<T>> predicates);
Predicates.or(Predicate<T> ...predicates);
使用Predicates.not方法
Predicates.not接受一個Predicate實例而且執行這個Predicate實例的邏輯否的功能。加入咱們想得到人口大於500,000的城市。使用這個方法能夠代替重寫一個Predicate實例:
Predicate largeCityPredicate = Predicates.not(smallPopulationPredicate);
使用Predicates.compose方法
Predicates.compose接受一個Function實例和一個Predicate實例做爲參數,而且從Fucntion實例當中返回的對象傳入到Predicate對象當中而且進行評估。在下面的例子當中,咱們建立一個新的Predicate對象:
public class SouthwestOrMidwestRegionPredicate implements Predicate<State> { 
  @Override
  public boolean apply(State input) {
    return input.getRegion().equals(Region.MIDWEST) ||
        input.getRegion().equals(Region.SOUTHWEST);
  }
}
下一步,咱們將使用原來的Function實例lookup來建立一個State對象,而且使用上面例子的Predicate實例來評估下這個State是否在MIDWEST或者SOUTHWEST:
Predicate<String> predicate = Predicates.compose(southwestOrMidwestRegionPredicate,lookup);
使用Supplier接口
Supplier接口只有一個方法以下:
public interface Supplier<T> {
  T get();
}
get方法返回泛型T的實例。Supplier接口能夠幫助咱們實現幾個典型的建立模式。當get方法被調用,咱們能夠返回相同的實例或者每次調用都返回新的實例。Supplier也可讓你靈活選擇是否當get方法調用的時候才建立實例。而且Supplier是個接口,單元測試也會更簡單,相對於其餘方法建立的對象,如靜態工廠方法。 總之,供應Supplier接口的強大之處在於它抽象的複雜性和對象如何須要建立的細節,讓開發人員自由地在他以爲任何方式建立一個對象時最好的方法。讓咱們看看如何Supplier接口。
 
一個Supplier接口的例子
下面代碼是Supplier接口的例子:
public class ComposedPredicateSupplier implements Supplier<Predicate<String>> {
  @Override
  public Predicate<String> get() {
    City city = new City("Austin,TX","12345",250000, Climate.SUB_ TROPICAL, 45.3);
    State state = new State("Texas","TX", Sets.newHashSet(city), Region.SOUTHWEST); 
    City city1 = new City("New York,NY","12345",2000000,Climate.TEMPERATE, 48.7); 
    State state1 = new State("New York","NY",Sets.newHashSet(city1), Region.NORTHEAST);
    Map<String,State> stateMap = Maps.newHashMap();
    stateMap.put(state.getCode(),state);
    stateMap.put(state1.getCode(),state1);
    Function<String,State> mf = Functions.forMap(stateMap);
    return Predicates.compose(new RegionPredicate(), mf);
  }
}
在這個例子當中,咱們看到使用Functions.forMap關鍵一個Function實例能夠經過State的縮寫來查找State,和使用Predicate實例來評估在那些地方是否有這個State。而後將Function實例和Predicate實例做爲參數傳入到Predicates.compose方法當中而且返回指望的Predicate實例。咱們使用了兩個靜態方法,Maps.newHashMap() 和Sets. newHashSet(),這兩個均可以在Guava的包當中找到咱們下一章會講到。如今咱們每次調用都會返回新的實例。咱們也能夠把建立Predicate實例的工做放到ComposedPredicateSupplier 的構造方法中來進行,當每次調用get的時候返回相同的實例。接着往下看,Guava提供了更簡單的選擇。
 
一個Suppliers類
正如咱們所指望的Guava,有一個Suppliers類的靜態方法來操做Supplier實例。在前面的例子,每次調用get方法都會返回一個新的實例。若是咱們想改變咱們的實現而且每次返回相同的實例,Suppliers給咱們一些可選項。
 
使用Suppliers.memoize方法
Suppliers.memoize方法返回一個包裝了委託實現的Supplier實例。當第一調用get方法,會被調用真實的Supplier實例的get方法。memoize方法返回被包裝後的Supplier實例。包裝後的Supplier實例會緩存調用返回的結果。後面的調用get方法會返回緩存的實例。咱們能夠這樣使用Suppliers.memoize方法:
Supplier<Predicate<String>> wrapped = Suppliers.memoize(composedPredicateSupplier);
只增長一行代碼咱們就能夠返回相同的實例。
 
使用Suppliers.memoizeWithExpiration方法
Suppliers.memoizeWithExpiration方法與memoize方法工做相同,只不過緩存的對象超過了時間就會返回真實Supplier實例get方法返回的值,在給定的時間當中緩存而且返回
Supplier包裝對象。注意這個實例的緩存不是物理緩存,包裝後的Supplier對象當中有真實Supplier對象的值。 例如:
Supplier<Predicate<String>> wrapped = Suppliers.memoizeWithExpiration(composedPredicateSupplier,10L,TimeUnit.MINUTES);
這裏咱們包裝了Supplier而且設置了超時時間爲10分鐘。對於ComposedPredicateSupplier來講沒什麼不一樣,可是Supplier返回的對象可能不一樣,可能從數據庫當中恢復,例如memoizeWithExpiration方法會很是有用。
經過依賴注入來使用Supplier接口是強有力的組合。然而,若是你使用Guice(google的依賴注入框架),它包含了Provider<T>接口提供了跟
Supplier<T>接口相同的功能。固然,若是你想利用緩存這個特性你可使用Supplier接口。
 
概要
咱們看到可使用Function接口和Predicate接口來在java編程當中添加一些函數方便的功能。Function接口提升給咱們轉換對象和Predicate接口能夠給咱們一個強大的過濾機。Functions和Predicates類也幫助咱們寫代碼更加簡單。Suppliers經過提供必要的協做對象,而徹底隱藏了這些對象建立的細節。 使用依賴注入框架spring或者guice,這些接口將容許咱們無縫地經過簡單地提供不一樣的實現改變咱們的程序的行爲。下一章咱們講Guava的重點,Collections。
相關文章
相關標籤/搜索