这基本上是this answer of mine的后续行动。
假设我正在研究自定义收集器,并且假设accumulator
始终会向供应商返回的集合中添加一些元素,那么当{{1} },中间结果之一将为空?一个例子可能更容易理解。
假设我有一个数字combiner
,我想将其拆分为列表列表,其中List
是分隔符。例如,我有2
,结果应该是1, 2, 3, 4, 2, 8
。实现起来并不是很复杂(不要过多地判断代码,我写得很快,只是为了写这个问题)。
[[1], [3, 4], [8]]
在此示例中,这可能无关紧要,但问题是:List<List<Integer>> result = Stream.of(1, 2, 3, 4, 2, 8)
.collect(Collector.of(
() -> new ArrayList<>(),
(list, elem) -> {
if (list.isEmpty()) {
List<Integer> inner = new ArrayList<>();
inner.add(elem);
list.add(inner);
} else {
if (elem == 2) {
list.add(new ArrayList<>());
} else {
List<Integer> 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-一种关联,无干扰,无状态的函数,它接受两个部分结果容器并将其合并。
对我来说 partial 表示在他们到达NO
之前,他们曾被accumulator
呼叫,但只是想确定一下。
答案 0 :(得分:10)
不保证在合并之前将累加器应用于容器。换句话说,要合并的列表可能为空。
对此进行演示:
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
当筛选器操作的结果尚不清楚时,工作负载拆分发生在源列表中。每个块都以相同的方式处理,而无需重新检查是否有任何元素到达累加器。
请注意,从Java 9开始,您还可以执行类似的操作
IntStream.range(0, 10).parallel().boxed()
.collect(Collectors.filtering(i -> i >= 3 && i < 7, Collectors.toList()));
这是收集器(此处为toList()
收集器)准备好遇到空容器的另一个原因,因为过滤发生在Stream
实现之外,并且在accept
上进行了调用复合收集器的累加器并不总是暗示对下游收集器的累加器进行accept
调用。
Collector
documentation中指定了能够处理空容器的要求:
为确保顺序执行和并行执行产生相等的结果,收集器函数必须满足 identity 和associativity约束。
同一性约束说,对于任何部分累加的结果,将其与空结果容器组合必须产生等效的结果。也就是说,对于任何一系列累加器和组合器调用的结果,部分累加的结果
a
而言,a
必须等于combiner.apply(a, supplier.get())
。