本文翻译自Getting Started with Google Guava这本书,如有翻译不足的地方请指出。

在这一章,我们开始注意到使用Guava进行编写代码会更加简单。我们将看看如何使用Guava当中的接口和类可以帮助我们,通过应用行之有效的模式,以使我们的代码更容易维护以及健壮。

在本章中我们将包含一下几点:
  • 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。

使用Guava进行函数式编程的更多相关文章

  1. Guava 是个风火轮之函数式编程(3)——表处理

    云栖社区> 博客列表> 正文 Guava 是个风火轮之函数式编程(3)--表处理 潘家邦 2016-01-26 13:19:21 浏览1062 评论0 java Guava 摘要: 早先学 ...

  2. guava函数式编程

    [Google Guava] 4-函数式编程 原文链接 译文链接 译者:沈义扬,校对:丁一 注意事项 截至JDK7,Java中也只能通过笨拙冗长的匿名类来达到近似函数式编程的效果.预计JDK8中会有所 ...

  3. Java经典类库-Guava中的函数式编程讲解

    如果我要新建一个java的项目,那么有两个类库是必备的,一个是junit,另一个是Guava.选择junit,因为我喜欢TDD,喜欢自动化测试.而是用Guava,是因为我喜欢简洁的API.Guava提 ...

  4. guava function and predicate 函数式编程

    @Test public void function(){ List<String> list = Lists.newArrayList("1","2&quo ...

  5. Guava 教程(3):Java 的函数式编程,通过 Google Collections 过滤和调用

    原文出处: oschina 在本系列博客的第一.二部分,我介绍了非常优秀的Google Collections和Guava包.本篇博客中我们来看看如何使用Google Collections来做到过滤 ...

  6. JDK 8 函数式编程入门

    目录 1. 概述 1.1 函数式编程简介 1.2 Lambda 表达式简介 2. Lambda 表达式 2.1 Lambda 表达式的形式 2.2 闭包 2.3 函数接口 3. 集合处理 3.1 St ...

  7. java8函数式编程(转载)

    1. 概述 1.1 函数式编程简介 我们最常用的面向对象编程(Java)属于命令式编程(Imperative Programming)这种编程范式.常见的编程范式还有逻辑式编程(Logic Progr ...

  8. angular2系列教程(六)两种pipe:函数式编程与面向对象编程

    今天,我们要讲的是angualr2的pipe这个知识点. 例子

  9. [学习笔记]JavaScript之函数式编程

    欢迎指导与讨论:) 前言 函数式编程能使我们的代码结构变得简洁,让代码更接近于自然语言,易于理解. 一.减少不必要的函数嵌套代码 (1)当存在函数嵌套时,若内层函数的参数与外层函数的参数一致时,可以这 ...

随机推荐

  1. maven+hudson构建集成测试平台

     1.下载hudson.war.2.命令行运行:java -jar hudson.war --httpPort=8070 -Dorg.eclipse.jetty.util.URI.charset=GB ...

  2. C# mongodb 1

    转载C# mongodb 概述 MongoDB是一个高性能,开源,无模式的文档型数据库,使用C++开发.是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.他 ...

  3. SVN & Git (二)

    Git:是一款免费.开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目. Git是一个开源的分布式版本控制系统,用以有效.高速的处理从很小到非常大的项目版本管理.Git 是 Linus T ...

  4. easyui dataBox 增加一天,减少一天

    <table> <tr> <td><a href="javascript:void(0)" class="easyui-link ...

  5. 探讨css中repaint和reflow

    (个人blog迁移文章.) 前言: 页面设计中,不可避免的需要浏览器进行repaint和reflow.那到底什么是repaint和reflow呢.下面谈谈自己对repaint和reflow的理解,以及 ...

  6. Swift语言指南(十)--字符串与字符

    原文:Swift语言指南(十)--字符串与字符 字符串是一段字符的有序集合,如"hellow,world"或"信天翁".Swift 中的字符串由 String ...

  7. leetcode[70] Simplify Path

    题目的意思是简化一个unix系统的路径.例如: path = "/home/", => "/home"path = "/a/./b/../../ ...

  8. Sql开发技巧

    原文:Sql开发技巧 简介 本文主要介绍下述几个技巧: 使用Row_Number分页 事务 根据条件刷选记录的技巧 分页 主要是使用了Row_Number()这个函数.一般如下: declare @P ...

  9. Python框架之Django学习

    当前标签: Django   Python框架之Django学习笔记(十四) 尛鱼 2014-10-12 13:55 阅读:173 评论:0     Python框架之Django学习笔记(十三) 尛 ...

  10. sbt公布assembly解决jar包冲突 deduplicate: different file contents found in the following

    一个.问题定义 近期使用sbt战斗assembly发生故障时,包,在package什么时候,发生jar包冲突/文件冲突,两个相同class来自不同jar包classpath内心冲突. 有关详细信息:我 ...