函数式编程
注意事项
截至 JDK7,Java 中也只能通过笨拙冗长的匿名类来达到近似函数式编程的效果。预计 JDK8 中会有所改变,但 Guava 现在就想给 JDK5 以上用户提供这类支持。
过度使用 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.JAVA_UPPER_CASE.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 一致。
特殊的断言
字符类型有自己特定版本的 Predicate——CharMatcher,它通常更高效,并且在某些需求方面更有用。CharMatcher 实现了 Predicate
此外,对可比较类型和基于比较逻辑的 Predicate,Range 类可以满足大多数需求——它表示一个不可变区间。Range 类实现了 Predicate,用以判断值是否在区间内。例如,Range.atMost(2)就是个完全合法的 Predicate
操作 Functions 和 Predicates
Functions 提供简便的 Function 构造和操作方法,包括:
forMap(Map<A, B>) | compose(Function<B, C>, Function<A, B>) | constant(T) |
identity() | toStringFunction() |
细节请参考 Javadoc。
相应地,Predicates 提供了更多构造和处理 Predicate 的方法,下面是一些例子:
细节请参考Javadoc。
使用函数式编程
Guava 提供了很多工具方法,以便用 Function 或 Predicate 操作集合。这些方法通常可以在集合工具类找到,如 Iterables,Lists,Sets,Maps,Multimaps 等。
断言
断言的最基本应用就是过滤集合。所有 Guava 过滤方法都返回”视图”——译者注:即并非用一个新的集合表示过滤,而只是基于原集合的视图。
*List 的过滤视图被省略了,因为不能有效地支持类似 get(int)的操作。请改用 Lists.newArrayList(Collections2.filter(list, predicate))做拷贝过滤。
除了简单过滤,Guava 另外提供了若干用 Predicate 处理 Iterable 的工具——通常在 Iterables 工具类中,或者是 FluentIterable 的”fluent”(链式调用)方法。
Iterables方法签名 | 说明 | 另请参见 |
boolean all(Iterable, Predicate) | 是否所有元素满足断言?懒实现:如果发现有元素不满足,不会继续迭代 | Iterators.all(Iterator, Predicate)FluentIterable.allMatch(Predicate) |
boolean any(Iterable, Predicate) | 是否有任意元素满足元素满足断言?懒实现:只会迭代到发现满足的元素 | Iterators.any(Iterator, Predicate)FluentIterable.anyMatch(Predicate) |
T find(Iterable, Predicate) | 循环并返回一个满足元素满足断言的元素,如果没有则抛出 NoSuchElementException |
Iterators.find(Iterator, Predicate) Iterables.find(Iterable, Predicate, T default) Iterators.find(Iterator, Predicate, T default) |
Optional<T> tryFind(Iterable, Predicate) | 返回一个满足元素满足断言的元素,若没有则返回Optional.absent() |
Iterators.find(Iterator, Predicate) Iterables.find(Iterable, Predicate, T default) Iterators.find(Iterator, Predicate, T default) |
indexOf(Iterable, Predicate) | 返回第一个满足元素满足断言的元素索引值,若没有返回-1 | Iterators.indexOf(Iterator, Predicate) |
removeIf(Iterable, Predicate) | 移除所有满足元素满足断言的元素,实际调用Iterator.remove()方法 | Iterators.removeIf(Iterator, Predicate) |
函数
到目前为止,函数最常见的用途为转换集合。同样,所有的 Guava 转换方法也返回原集合的视图。
*Map 和 Multimap 有特殊的方法,其中有个 EntryTransformer<K, V1, V2>参数,它可以使用旧的键值来计算,并且用计算结果替换旧值。
对 Set 的转换操作被省略了,因为不能有效支持 contains(Object)操作——译者注:懒视图实际上不会全部计算转换后的 Set 元素,因此不能高效地支持 contains(Object)。*请改用 Sets.newHashSet(Collections2.transform(set, function))进行拷贝转换。
List<String> names;
Map<String, Person> personWithName;
List<Person> people = Lists.transform(names, Functions.forMap(personWithName));
ListMultimap<String, String> firstNameToLastNames;
// maps first names to all last names of people with that first name
ListMultimap<String, String> firstNameToName = Multimaps.transformEntries(firstNameToLastNames,
new EntryTransformer<String, String, String> () {
public String transformEntry(String firstName, String lastName) {
return firstName + " " + lastName;
}
});
可以组合 Function 使用的类包括:
Ordering | Ordering.onResultOf(Function) |
Predicate | Predicates.compose(Predicate, Function) |
Equivalence | Equivalence.onResultOf(Function) |
Supplier | Suppliers.compose(Function, Supplier) |
Function | Functions.compose(Function, Function) |
此外,ListenableFuture API 支持转换 ListenableFuture。Futures 也提供了接受 AsyncFunction 参数的方法。
AsyncFunction 是 Function 的变种,它允许异步计算值。