[Guava使用教程]-函数式编程
注意事项
Java 8包含java.util.function和java.util.stream包,它代替Guava的函数式编程。
Guava的函数式编程可以在Java 8之前的Java版本上使用,也不像Java 8的函数编程需要笨拙而冗长地使用匿名类。
过度使用Guava的函数式编程习惯可能导致代码冗长、混乱、不可读和效率低下。这些都是Guava中最容易也是最常被滥用的部分,当您将代码变成“一行代码”时,Guava团队会哭。
比较如下代码:
Function<String, Integer> lengthFunction = new Function<String, Integer>() {
public Integer apply(String string) {
return string.length();
}
};
Predicate<String> allCaps = new Predicate<String>() {
public boolean apply(String string) {
return CharMatcher.javaUpperCase().matchesAllOf(string);
}
};
Multiset<Integer> lengths = HashMultiset.create(
Iterables.transform(Iterables.filter(strings, allCaps), lengthFunction));
FluentIterable的版本:
Multiset<Integer> lengths = HashMultiset.create(
FluentIterable.from(strings)
.filter(new Predicate<String>() {
public boolean apply(String string) {
return CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string);
}
})
.transform(new Function<String, Integer>() {
public Integer apply(String string) {
return string.length();
}
}));
Multiset<Integer> lengths = HashMultiset.create();
for (String string : strings) {
if (CharMatcher.JAVA_UPPER_CASE.matchesAllOf(string)) {
lengths.add(string.length());
}
}
即使用了静态导入,甚至把Function和Predicate的声明放到别的文件,第一种代码实现仍然不简洁,可读性差并且效率较低。
截至JDK7,命令式代码仍应是默认和第一选择。不应该随便使用函数式风格,除非你绝对确定以下两点之一:
- 使用函数式风格以后,整个工程的代码行会净减少。在上面的例子中,函数式版本用了11行, 命令式代码用了6行,把函数的定义放到另一个文件或常量中,并不能帮助减少总代码行。
- 为了提高效率,转换集合的结果需要懒视图,而不是明确计算过的集合。此外,确保你已经阅读和重读了Effective Java的第55条,并且除了阅读本章后面的说明,你还真正做了性能测试并且有测试数据来证明函数式版本更快。
请务必确保,当使用Guava函数式的时候,用传统的命令式做同样的事情不会更具可读性。尝试把代码写下来,看看它是不是真的那么糟糕?会不会比你想尝试的极其笨拙的函数式 更具可读性。
Functions[函数]和Predicates[断言]
本节只讨论直接与Function和Predicate打交道的Guava功能。一些其他工具类也和”函数式风格”相关,例如 Iterables.concat(Iterable),和其他用常量时间返回视图的方法。尝试看看集合工具类。
Guava提供两个基本的函数式接口:
- Function<A, B>,它声明了单个方法B apply(A input)。Function对象通常被预期为引用透明的——没有副作用——并且引用透明性中的”相等”语义与equals一致,如a.equals(b)意味着function.apply(a).equals(function.apply(b))。
- Predicate,它声明了单个方法 boolean apply(T input)。Predicate 对象通常也被预期为无副作用函数,并且”相等”语义与equals一致。
特殊的Predicates
字符类型有自己特定版本的Predicate——CharMatcher,它通常更高效,并且在某些需求方面更有用。CharMatcher实现了Predicate,可以当作 Predicate一样使用,要把Predicate转成CharMatcher,可以使用CharMatcher.forPredicate。更多细节请参考字符串处理。
此外,对可比较类型和基于比较逻辑的Predicate,Range类可以满足大多数需求——它表示一个不可变区间。Range类实现了Predicate,用以判断值是否在区间内。例如,Range.atMost(2)就是个完全合法的 Predicate。更多使用Range的细节。
操作Functions和Predicates
在Functions, 提供了简单的函数构造和使用方法,包括:
Predicates中可用的构造和使用方法要多得多,示例包括:
- instanceOf(Class)
- assignableFrom(Class)
- contains(Pattern)
- in(Collection)
- isNull()
- alwaysFalse()
- alwaysTrue()
- equalTo(Object)
- compose(Predicate, Function)
- and(Predicate...)
- or(Predicate...)
- not(Predicate)
有关详细信息,请参考Javadoc。
使用函数式编程
Guava提供了许多使用函数式编程操作集合的工具。这些通常可以在集合实用程序类Iterables, Lists, Sets, Maps, Multimaps等找到。
Predicates最基本的用途是过滤集合。所有Guava过滤器方法都返回视图。