【问题标题】:stream collect accumulator/combiner order流收集累加器/组合器订单
【发布时间】:2018-09-05 09:33:38
【问题描述】:

这基本上是this answer of mine的后续。

假设我正在开发自定义收集器并假设accumulator 总是 将向供应商返回的集合添加一些元素,当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 吗?我真的很倾向于说NO,因为在文档中这些被称为:

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

嗯,部分对我来说表明accumulator 在他们到达combiner 之前被调用,但只是想确定一下。

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

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

    为了证明这一点:

    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 调用不会' t 总是暗示在下游收集器的累加器上调用accept

    Collector documentation中规定了能够处理空容器的要求:

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

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

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 2015-05-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多