Java 实现Collector接口,由于 java.util.stream.Collectors
类提供的工厂方法无法满足需要,用户希望手动实现 java.util.stream.Collector
接口。为工厂方法 Collector.of
传入的 supplier
、accumulator
、combiner
、finisher
函数提供 lambda 表达式或方法引用,以及其他所需的特性。
Java 实现Collector接口 问题描述
由于 java.util.stream.Collectors
类提供的工厂方法无法满足需要,用户希望手动实现 java.util.stream.Collector
接口。
Java 实现Collector接口 解决方案
为工厂方法 Collector.of
传入的 supplier
、accumulator
、combiner
、finisher
函数提供 lambda 表达式或方法引用,以及其他所需的特性。
Java 实现Collector接口 具体实例
Collectors
工具类定义了多种便利的静态方法,它们的返回类型为 Collector
。这些方法包括 toList
、toSet
、toMap
以及 toCollection
,相关讨论请参见其他章节。实现 Collector
的类的实例作为 Stream.collect
方法的参数。如例 4-34 所示,evenLengthStrings
方法传入字符串参数,并返回仅包含偶数长度字符串的 List
。
例 4-34 利用
collect
方法返回List
public List<String> evenLengthStrings(String... strings) {
return Stream.of(strings)
.filter(s -> s.length() % 2 == 0)
.collect(Collectors.toList()); ➊
}
➊ 将偶数长度的字符串收集到 List
中
编写自定义收集器的过程则略显复杂。收集器使用 5 个函数,它们的作用是将条目累加到可变容器,并有选择性地对结果进行转换。这 5 个函数是 supplier
、accumulator
、combiner
、finisher
以及 characteristics
。
我们首先讨论 characteristics
函数,表示 Collector.Characteristics
枚举的一个不可变的元素 Set
。三个枚举常量为 CONCURRENT
、IDENTITY_FINISH
与 UNORDERED
。CONCURRENT
表示结果容器支持多个线程在结果容器上并发地调用累加器函数,UNORDERED
表示集合操作无须保留元素的出现顺序(encounter order),IDENTITY_FINISH
表示终止器函数返回其参数而不做任何修改。
请注意,如果默认值就是实际需要的,则不必提供任何特性。下面列出了每种参数的用途。
supplier()
使用 Supplier<A>
创建累加器容器(accumulator container)。
accumulator()
使用 BiConsumer<A,T>
为累加器容器添加一个新的数据元素。
combiner()
使用 BinaryOperator<A>
合并两个累加器容器。
finisher()
使用 Function<A,R>
将累加器容器转换为结果容器。
characteristics()
从枚举值中选择的 Set<Collector.Characteristics>
。
如果读者熟悉 java.util.function
包定义的函数式接口,则不难理解各个参数的含义: Supplier
用于创建累加临时结果所用的容器;BiConsumer
用于将一个元素添加到累加器; BinaryOperator
表示输入类型和输出类型相同,因此可以将两个累加器合二为一;最后,Function
将累加器转换为所需的结果容器。
程序在收集过程中调用上述方法,它们由 Stream.collect
这样的方法触发。从概念上讲,集合过程相当于例 4-35 所示的(泛型)代码(取自 Javadoc)。
例 4-35 各种
Collector
方法的用法
R container = collector.supplier.get(); ➊
for (T t : data) {
collector.accumulator().accept(container, t); ➋
}
return collector.finisher().apply(container); ➌
❶ 创建累加器容器
❷ 将每个元素添加到累加器容器
❸ 通过 finisher
函数将累加器容器转换为结果容器
本例并未出现 combiner
函数,这或许令人感到困惑。如果处理的是顺序流,则不需要该函数,算法将既定方式执行。如果处理的是并行流,流将被分为多个子流,每个子流都会生成各自的累加器容器。接下来,在连接过程中使用 combiner
函数将多个累加器容器合并为一个,然后应用 finisher
函数。
例 4-36 显示了与例 4-34 类似的代码。
例 4-36 利用
collect
方法返回不可修改的SortedSet
public SortedSet<String> oddLengthStringSet(String... strings) {
Collector<String, ?, SortedSet<String>> intoSet =
Collector.of(TreeSet<String>::new, ➊
SortedSet::add, ➋
(left, right) -> { ➌
left.addAll(right);
return left;
},
Collections::unmodifiableSortedSet); ➍
return Stream.of(strings)
.filter(s -> s.length() % 2 != 0)
.collect(intoSet);
}
❶ Supplier
:创建新的 TreeSet
❷ BiConsumer
:将每个字符串添加到 TreeSet
❸ BinaryOperator
:将两个 SortedSet
实例合二为一
❹ finisher
:创建不可修改的 Set
程序将输出一个经过排序且不可修改的字符串集,它按字典序排序。
本例展示了如何通过 Collector.of
方法生成收集器。of
方法包括以下两种形式:
static <T,A,R> Collector<T,A,R> of(Supplier<A> supplier,
BiConsumer<A,T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Collector.Characteristics... characteristics)
static <T,R> Collector<T,R,R> of(Supplier<R> supplier,
BiConsumer<R,T> accumulator,
BinaryOperator<R> combiner,
Collector.Characteristics... characteristics)
Collectors
类提供了多种用于生成收集器的便利方法,用户几乎不需要创建自定义收集器,不过掌握相关的知识仍然很有必要。综合应用 java.util.function
包定义的函数式接口,可以创建各种有趣的对象。