自定义收集器尽管 JDK 提供的标准的收集器集合非常大,但编写您自己的收集器非常容易。Collector 接口(如清单 4 所示)非常简单。该接口通过 3 种类型来实现参数化:输入类型 T、累加器类型 A 和最终的返回类型 R(A 和 R 通常是相同的),这些方法返回的函数与之前演示的 collect() 3 参数版本所接受的函数类似。
清单 4. Collector 接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public interface Collector<T, A, R> {
/** Return a function that creates a new empty result container */
Supplier<A> supplier();
/** Return a function that incorporates an element into a container */
BiConsumer<A, T> accumulator();
/** Return a function that merges two result containers */
BinaryOperator<A> combiner();
/** Return a function that converts the intermediate result container
into the final representation */
Function<A, R> finisher();
/** Special characteristics of this collector */
Set<Characteristics> characteristics();
}
|
Collectors 中的大部分收集器工厂的实现都很简单。例如,toList() 的实现是:
1
2
3
4
| return new CollectorImpl<>(ArrayList::new,
List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
|
此实现使用 ArrayList 作为结果容器,使用 add() 合并一个元素,并使用 addAll() 将一个列表合并到另一个中,通过这些特征表明它的完成函数是身份函数(这使得流框架可以优化执行)。
与之前看到的一样,一些一致性要求与缩减中的身份和累加器函数之间的限制类似。这些要求已在 Collector 的规范中列出。
作为一个更复杂的示例,可以考虑在数据集上创建汇总统计数据的问题。很容易使用缩减来计算数字数据集的总和、最小值、最大值或数量(而且您可以使用总和和数量来计算平均值)。在数据上,使用缩减在一轮计算中一次性计算所有这些结果更加困难。但您可以轻松地编写一个 Collector 来高效地(如果愿意,还可并行地)执行此计算。
Collectors 类包含一个 collectingInt() 工厂方法,该方法返回一个 IntSummaryStatistics,后者会执行您想要的准确操作,比如在一轮计算中计算 sum、min、max、count 和 average。IntSummaryStatistics 的实现很简单,而且您可以轻松地编写自己的类似收集器来计算任意数据汇总结果(或扩展此结果)。
清单 5 显示了 IntSummaryStatistics 类。实际实现包含更多细节(包含用于获取汇总统计数据的 getter),但它的核心是简单的 accept() 和 combine() 方法。
清单 5. summarizingInt() 收集器使用的 IntSummaryStatistics 类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| public class IntSummaryStatistics implements IntConsumer {
private long count;
private long sum;
private int min = Integer.MAX_VALUE;
private int max = Integer.MIN_VALUE;
public void accept(int value) {
++count;
sum += value;
min = Math.min(min, value);
max = Math.max(max, value);
}
public void combine(IntSummaryStatistics other) {
count += other.count;
sum += other.sum;
min = Math.min(min, other.min);
max = Math.max(max, other.max);
}
// plus getters for count, sum, min, max, and average
}
|
如您所见,这是一个非常简单的类。在观察每个新数据元素时,会以各种方式更新各种汇总结果,而且会以各种方式组合两个 IntSummaryStatistics 持有者。Collectors.summarizingInt() 的实现(如清单 6 所示)同样很简单;它创建了一个 Collector,以便通过应用一个整数值来提取器函数,并将结果传递给 IntSummaryStatistics.accept() 来合并一个元素。
清单 6. summarizingInt() 收集器工厂1
2
3
4
5
6
7
8
| public static <T>
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {
return new CollectorImpl<T, IntSummaryStatistics, IntSummaryStatistics>(
IntSummaryStatistics::new,
(r, t) -> r.accept(mapper.applyAsInt(t)),
(l, r) -> { l.combine(r); return l; },
CH_ID);
}
|
组合收集器的容易性(您在 groupingBy() 示例中已看到)和创建新收集器的容易性相结合,可以创建流数据的几乎任何汇总结果,同时保持您的代码紧凑而又清晰。 |