stream collect accumulator/combiner order

走远了吗. 提交于 2019-12-03 03:02:25

There is no guaranty that the accumulator has been applied to a container before merging. In other words, the lists to merge may be empty.

To demonstrate this:

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);
         });

On my machine, it prints:

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

The workload splitting happens at the source list, when the outcome of the filter operation is not known yet. Each chunk is processed the same way, without rechecking whether any element has arrived the accumulator.

Mind that starting with Java 9, you can also do something like

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

which is another reason why a collector (here, the toList() collector) should be prepared to encounter empty containers, as the filtering happens outside the Stream implementation and an accept call on the compound collector’s accumulator doesn’t always imply an accept call on the downstream collector’s accumulator.

The requirement of being able to handle empty containers is specified in the Collector documentation:

To ensure that sequential and parallel executions produce equivalent results, the collector functions must satisfy an identity and an associativity constraints.

The identity constraint says that for any partially accumulated result, combining it with an empty result container must produce an equivalent result. That is, for a partially accumulated result a that is the result of any series of accumulator and combiner invocations, a must be equivalent to combiner.apply(a, supplier.get()).

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!