Guava翻译之Functional-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

Guava翻译之Functional

简介: guava翻译系列

使用guava进行函数式编程

在这一章,我们将关注使用guava将我们的开发变得更加容易,我们下面将学习具体的接口和类让我们的程序的扩展性,健壮性更好。
我们这一章里面将覆盖一下几个主题:

  • Function 接口: 介绍怎样在java中使用函数式编程,并且讨论一下函数式编程的最佳实践
  • Functions 类: Functions 是一个包含了一些实现Function接口的静态方法的集合
  • Predicate 接口:Predicate 接口是计算一个对象是否满足条件,如果满足条件则返回True
  • predicates 类: Predicates 是一个实现了 Predicate接口的静态工具类
  • Supplier 接口:
  • Suppliers 类: Suppliers 是提供了Supplier接口的默认实现类

使用 Function 接口

函数式编程强调实用functions实现其目标而改变状态。This is in contrast with imperative programming that typically relies on the changing state and is the approach that is familiar with most of thedevelopers. Guava中的Function接口给了我们在java中使用函数式编程的能力。
Function 仅仅包含两个方法:

public interface Function<F,T> {
T apply(F input);
boolean equals(Object object);
}

我们不会过多的讨论equals方法,ObjetA 和 Object B equals 的条件就是 在 Object A上调用 apply 方法得到得结果和在 Object B上调用 apply方法得到的结果是相等的。 apply 方法接受一个输入的object 返回一个输出的object。 一个设计优秀的 Function的实现在经过apply调用后,输入参数应该保持不变,返回值可以是一个新的对象,而不应该改变输入参数的值然后返回。 下面是一个将java.util.date 对象转换成 对应的 String :

public class DateFormatFunction implements Function<Date,String> {
@Override
public String apply(Date input) {
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/mm/yyyy");
// 注意这里的 input 并没有变化
return dateFormat.format(input);
}
}

在上面的例子我们非常清晰的看到了将 java.util.date 通过使用SimpleDateFormat转换成了我们期望的String格式。 这个例子有点过于简单,但是其主要目的是为了向我们展示使用Function接口的目的,就是将转换的细节隐藏。 这个例子中我们声明了一个实现了Function的类,我们也可以很简单使用匿名类实现。 就想下面展示的那样:

Function<Date,String> function = new Function<Date, String>() {
@Override
public String apply( Date input) {
return new
SimpleDateFormat("dd/mm/yyyy").format(input);
}
};

上面的这两个其实没有什么本质的区别,一个是声明了一个类,一个是使用匿名类实现。

使用Function 接口的方针

这是一个非常好的时机去讨论 在代码中使用Function接口和匿名类。 目前的java版本中,还没有在其他语言已经存在的闭包特性。在即将发布的java 8 版本中将会出现这一特性,目前在java实现闭包的方式是使用匿名类。 虽然使用匿名类和使用闭包一样高效,但是笨重的语法,并且使用了多了之后,代码将变个晦涩难懂也更难维护。 事实上,我们来分析一下前面的例子,虽然他展示了Functions的使用方法,但是我们并没有从中感觉到好处,实现这样的功能,我们觉得下面这样写更方便:

public String formatDate(Date input) {
return new SimpleDateFormat("dd/mm/yyyy").format(input);
}

和前面的使用匿名类实现的方式相比,最后一个例子的写法更容易理解。 什么时候使用 Function 取决于你希望在哪里进行日期转换。 如果你有一个包含了 Date 变量和一个将date 转换成 String的方法,那么还是最好使用最后一种方式.但是如果你有一个集合的Date对象需要是用字符串表示,使用Function 接口就会比较方便。 这里主要要强调的一点就是,我们不能为了使用Function而使用Function。 在使用Function之前考虑一下,我们能从使用Function的过程中获取什么样的好处。 当我们后面学习guava的collections,cache的时候,我们将看到一些合适的例子。

使用 Functions 类

Functions 类包含了一些有用的方法,在这一小节中,我们将介绍两个非常有用的方法来提高gauva的生产效率。

使用 Functions.forMap方法

forMap接受一个Map,返回一个function(Function), apply 方法会查询map,下面例子中的State类,代表了 United States:

public class State {
private String name;
private String code;
private Set<City> mainCities = new HashSet<City>();
}

现在我们假设我们有一个Map,名字是stateMap,他的组成形式是Map 我们可以根据key找到对应的State. 下面我们看一个具体的例子:

Function<String,State> lookup = Functions.forMap(stateMap);
//Would return State object for NewYork
lookup.apply("NY");

关于Functions.forMap方法的使用有一些注意的地方,如果在stateMap中根据指定的key没有找到对应的state,那么会抛出IllegalArgumentException 异常,但是还有了;另外一种形式的Functions.forMap(),可以指定一个defaultValue,当根据key没有找到对应的value时,就会返回默认的值。 使用Function 接口我们可以很简单的替换具体的实现类。 我们也可以配合Splitter(我们上一章提到的),是的guava变得更加锋利。

使用Functions.compose方法

现在想像一下你有另外一个类,这个类代表了一个城市city,类的定义如下:

public class City {
private String name;
private String zipCode;
private int population;
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下面的MainCities的缩写,Guava提供一个更加优秀的方法,它就是Functions.compose 方法,这个方法就是两个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".

让我们花几分钟来看一下这段代码的调用过程,从结果我们可以看出,先是调用了lookup.apply()的方法,将其的返回值作为stateFunction的输入参数,stateFunction.apply()的放回结果就是我们想要的结果. 总的来说,简化班的调用过程是如下的代码:

String cities = stateFunction.apply(lookup.apply("NY"));

使用Predicate接口

Predicate 接口和Function接口有点类似,Predicate接口也有两个方法,下面是Predicate接口的定义:

public interface Predicate<T> {
boolean apply(T input)
boolean equals(Object object)
}

和Function接口类似我们这里暂不详细的讨论equals方法。 Function 接口是用来转换Objects,而Predicate的apply方法是用来过滤objects。Predicate的使用准则和Function的使用准备差不多,不要滥用Predicate接口,再下一张的 guava 集合中,我们将看到Predicate的最佳实践。

Predicate 接口的例子

这个例子使用Predicate接口去过滤到人口小于 50000的城市。

public class PopulationPredicate implements Predicate<City> {
@Override
public boolean apply(City input) {
return input.getPopulation() <= 500000;
}
}

一般来说,Predicate用来在集合后过滤掉那些不符合条件的记录。 关于Predicate的使用基本和Function差不多。这里不在细说。

使用Predecates 类

Predicates 类是一个包含了许多有用方法的 Predicate实例对象, Predicates 类提供了 Predicates.and, Predicates.or,Predicates.not,Predicate.compose等方法,让我先从最简单的看起:

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;
}
}

一般来说我们使用Predicate 都会声明为匿名类,但是这里为了表达清楚,我们还是使用了具体的类。

使用Predicates.and 方法

Predicates.add方法接受 多个Predicate实例,返回单个Predicate实例,如果所有的实例的运算后的结果都是true,那么最后返回的结果就是true,如果其中的任意一个返回false了,那么后续的Predicate实例就不会再继续执行,让我们来看一个具体的例子:

Predicate smallAndDry =
Predicates.and(smallPopulationPredicate,lowRainFallPredicate);

对于多个Predicate实例我们也可以使用如下的方式:

Predicates.and(Iterable<Predicate<T>> predicates);
Predicates.and(Predicate<T> ...predicates);

使用Predicates.or方法

Predicates.or方法接受多个Predicate实例,返回一个Predicate实例,如果其中一个Predicate返回为true,那么最终的返回结果就是true,并且后续的Predicate实例对象也不会再继续执行。

对于多个Predicate实例我们也可以使用如下的方式:

Predicates.and(Iterable<Predicate<T>> predicates);
Predicates.and(Predicate<T> ...predicates);

使用Predicates.not方法

Predicates.not 方法接受一个Predicate实例,并且对这个实例的返回值取反. 简单的例子如下:

Predicate largeCityPredicate =
Predicate.not(smallPopulationPredicate);

使用Predicates.compose方法

Predicates.compose 接受一个 Function 和 Predicate实例作为参数, 使用 Function的返回参数作为 Predicate的输入参数,Predicate根据输入参数返回true或则false。 下面是一个简单的例子:

public class SouthwestOrMidwestRegionPredicate implements
Predicate<State> {
@Override
public boolean apply(State input) {
return input.getRegion().equals(Region.MIDWEST) ||
input.getRegion().equals(Region.SOUTHWEST);
}
}
Predicate<String> predicate =
Predicates.compose(southwestOrMidwestRegionPredicate,lookup);

使用 Supplier 接口

Supplier 接口只有一个方法:

public interface Supplier<T> {
T get();
}

get 方法返回 T类型的实例对象,并且只能返回T类型的对象。 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);
}
}

上面的代码中,我们结合了之前学习的Function,Predicate 等知识创建了一个新的对象。

使用 Suppliers 类

在缓存返回对象方面,Supperliers给我们提供一些简单的方法.

使用 Suppliers.memoize 方法

Suppliers的memoize方法在get()方法外面包装了一层,当第一次调用get()方法时,我们创建了一个新的实例,但是在返回之前我们做了一层代理,将对象缓存起来,然后再将对象返回出去,这下接下来的一些请求再过来时候,我们就直接返回缓存好的对象。 具体的代码如下

Supplier<Predicate<String>> wrapped =
Suppliers.memoize(composedPredicateSupplier);

使用Suppliers.memoizeWithExpiration方法

Suppliers.memoizeWithExpiration 其实就是在Supplier.memoize的基础上加上了缓存超时时间。 具体代码例子如下:

Supplier<Predicate<String>> wrapped =
Suppliers.memoize(composedPredicateSupplier,10L,TimeUnit.MINUTES);

这里的超时时间是10分钟。

总结

从上面的学习中,我们可以看到guava可以为java增加关于函数式编程的影响。 Function接口可以帮助我们transfer对象,Predicate 借口可以帮助我们过滤对象。 Supplier接口帮助我们隐藏了创建对象的细节。 通过结合使用依赖注入的方式,这些接口可以帮助我们屏蔽实现细节。 在下一章,我们将学习 guava collections。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章