使用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。
相关文章
相关标签/搜索