流收集累加器/合并器命令

这基本上是我的这个答案的后续行动。

假设我正在处理自定义收集器并假设accumulator 总是会向供应商返回的集合添加一些元素,是否有可能在调用combiner时,其中一个中间结果将为空? 一个例子可能更容易理解。

假设我有一个数字List ,我想将它拆分为列表列表,其中2是分隔符。 所以例如我有1, 2, 3, 4, 2, 8结果应该是[[1], [3, 4], [8]] 。 这实现起来并不复杂(不要过多地判断代码,我写了一些快速的东西,这样我就可以写出这个问题)。

 List<List> result = Stream.of(1, 2, 3, 4, 2, 8) .collect(Collector.of( () -> new ArrayList(), (list, elem) -> { if (list.isEmpty()) { List inner = new ArrayList(); inner.add(elem); list.add(inner); } else { if (elem == 2) { list.add(new ArrayList()); } else { List last = list.get(list.size() - 1); last.add(elem); } } }, (left, right) -> { // This is the real question here: // can left or right be empty here? return left; })); 

这可能与此示例无关,但问题是: combiner的元素可以是空List吗? 我真的很想拒绝,因为在文档中这些被称为:

combiner – 一个关联的,非干扰的无状态函数,它接受两个部分结果容器并合并它们。

好吧,对我来说, 偏向于表明accumulator在它们到达combiner之前被调用,但只是想确定。

在合并之前,没有保证累加器已应用于容器。 换句话说,要合并的列表可能是空的。

为了certificate这一点:

 IntStream.range(0, 10).parallel().boxed() .filter(i -> i >= 3 && i < 7) .collect(ArrayList::new, List::add, (l1,l2)->{ System.out.println(l1.size()+" + "+l2.size()); l1.addAll(l2); }); 

在我的机器上,它打印:

 0 + 0 0 + 0 0 + 0 1 + 1 0 + 2 0 + 2 1 + 1 2 + 0 2 + 2 

当filter操作的结果尚未知道时,工作负载拆分发生在源列表中。 每个块都以相同的方式处理,无需重新检查是否有任何元素已到达累加器。

请注意,从Java 9开始,您也可以执行类似的操作

 IntStream.range(0, 10).parallel().boxed() .collect(Collectors.filtering(i -> i >= 3 && i < 7, Collectors.toList())); 

这是收集器(这里, toList()收集器)应该准备遇到空容器的另一个原因,因为过滤发生在Stream实现之外,并且复合收集器的累加器上的accept调用并不总是意味着accept调用下游收集器的累加器。

Collector文档中指定了能够处理空容器的要求:

为确保顺序和并行执行产生相同的结果,收集器函数必须满足标识和关联约束。

标识约束表示对于任何部分累积的结果,将其与空结果容器组合必须产生等效结果。 也就是说,对于部分累积的结果,这是任何一系列累加器和组合器调用的结果, a必须等于combiner.apply(a, supplier.get())