normanli 2017-02-27 1399浏览量
在这一章,我们将关注使用guava将我们的开发变得更加容易,我们下面将学习具体的接口和类让我们的程序的扩展性,健壮性更好。
我们这一章里面将覆盖一下几个主题:
函数式编程强调实用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接口和匿名类。 目前的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 类包含了一些有用的方法,在这一小节中,我们将介绍两个非常有用的方法来提高gauva的生产效率。
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变得更加锋利。
现在想像一下你有另外一个类,这个类代表了一个城市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 接口和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接口去过滤到人口小于 50000的城市。
public class PopulationPredicate implements Predicate<City> {
@Override
public boolean apply(City input) {
return input.getPopulation() <= 500000;
}
}
一般来说,Predicate用来在集合后过滤掉那些不符合条件的记录。 关于Predicate的使用基本和Function差不多。这里不在细说。
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.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方法接受多个Predicate实例,返回一个Predicate实例,如果其中一个Predicate返回为true,那么最终的返回结果就是true,并且后续的Predicate实例对象也不会再继续执行。
对于多个Predicate实例我们也可以使用如下的方式:
Predicates.and(Iterable<Predicate<T>> predicates);
Predicates.and(Predicate<T> ...predicates);
Predicates.not 方法接受一个Predicate实例,并且对这个实例的返回值取反. 简单的例子如下:
Predicate largeCityPredicate =
Predicate.not(smallPopulationPredicate);
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 接口只有一个方法:
public interface Supplier<T> {
T get();
}
get 方法返回 T类型的实例对象,并且只能返回T类型的对象。 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 等知识创建了一个新的对象。
在缓存返回对象方面,Supperliers给我们提供一些简单的方法.
Suppliers的memoize方法在get()方法外面包装了一层,当第一次调用get()方法时,我们创建了一个新的实例,但是在返回之前我们做了一层代理,将对象缓存起来,然后再将对象返回出去,这下接下来的一些请求再过来时候,我们就直接返回缓存好的对象。 具体的代码如下
Supplier<Predicate<String>> wrapped =
Suppliers.memoize(composedPredicateSupplier);
Suppliers.memoizeWithExpiration 其实就是在Supplier.memoize的基础上加上了缓存超时时间。 具体代码例子如下:
Supplier<Predicate<String>> wrapped =
Suppliers.memoize(composedPredicateSupplier,10L,TimeUnit.MINUTES);
这里的超时时间是10分钟。
从上面的学习中,我们可以看到guava可以为java增加关于函数式编程的影响。 Function接口可以帮助我们transfer对象,Predicate 借口可以帮助我们过滤对象。 Supplier接口帮助我们隐藏了创建对象的细节。 通过结合使用依赖注入的方式,这些接口可以帮助我们屏蔽实现细节。 在下一章,我们将学习 guava collections。
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。
集结各类场景实战经验,助你开发运维畅行无忧