java – 可以在序列流上使用收集器的组合器函数吗?

示例程序:

public final class CollectorTest
{
    private CollectorTest()
    {
    }

    private static <T> BinaryOperator<T> nope()
    {
        return (t, u) -> { throw new UnsupportedOperationException("nope"); };
    }

    public static void main(final String... args)
    {
        final Collector<Integer, ?, List<Integer>> c
            = Collector.of(ArrayList::new, List::add, nope());

        IntStream.range(0, 10_000_000).boxed().collect(c);
    }
}

所以,为了简化这里的事情,没有最终的转换,所以结果代码很简单。

现在,IntStream.range()生成一个顺序流。我只是将结果输入到整数,然后我的设计收藏者将它们收集到列表<整数>中。很简单

并且无论我运行这个示例程序多少次,UnsupportedOperationException从不打,这意味着我的虚拟组合器从不被调用。

我有点期待这个,但是我已经误解了足够的流,我必须问问题…

当流保证顺序时,收集器的组合器是否可以被调用?

ReduceOps.java中的流实现代码的仔细阅读揭示了组合函数仅在ReduceTask完成时被调用,并且ReduceTask实例仅在并行评估管道时使用。因此,在当前实现中,在评估顺序流水线时,不会调用组合器。

然而,规范中没有保证这一点。收集器是对其实现提出要求的接口,并且没有为顺序流授予豁免。就个人而言,我觉得很难想象为什么连续管道评估可能需要调用组合器,但是有比我更多想象力的人可能会找到一个聪明的用途,并实现它。该规范允许它,即使今天的实现没有这样做,你仍然需要考虑。

这并不奇怪。流API的设计中心是通过顺序执行支持平行执行。当然,程序有可能观察是按顺序还是并行执行。但是API的设计是支持一种允许任何一种编程的风格。

如果您正在撰写收藏家,并发现编写关联组合器功能是不可能的(或不方便或困难),导致您要将流限制为顺序执行,也许这意味着您的方向错误。现在是回顾一下,考虑以不同的方式来处理这个问题。

不需要关联组合器功能的常见的还原式操作称为左折。主要特点是折叠功能严格从左到右,每次进行一次。我没有意识到并行化左侧的方式。

当人们试图通过我们一直在谈论的方式来扭曲收藏家时,他们通常会寻找像左撇子那样的东西。 Streams API没有对此操作的直接API支持,但它很容易编写。例如,假设要使用此操作减少字符串列表:重复第一个字符串,然后追加第二个字符串。很容易证明这个操作不是关联的:

List<String> list = Arrays.asList("a", "b", "c", "d", "e");

System.out.println(list.stream()
    .collect(StringBuilder::new,
             (a, b) -> a.append(a.toString()).append(b),
             (a, b) -> a.append(a.toString()).append(b))); // BROKEN -- NOT ASSOCIATIVE

顺序运行,这将产生所需的输出:

aabaabcaabaabcdaabaabcaabaabcde

但是当并行运行时,它可能会产生这样的东西:

aabaabccdde

由于它“顺序”,所以我们可以通过调用sequential()来强制执行这一操作,并通过使组合器抛出异常来备份它。另外,供应商必须被准确称为一次。没有办法结合中间的结果,所以如果供应商被叫两次,我们已经遇到麻烦了。但是由于我们“知道”供应商在连续模式下仅被称为一次,大多数人不用担心。事实上,我看到人们写的“供应商”返回一些现有的对象,而不是创建一个新的对象,违反了供应商合同。

在这种使用3-arg形式的collect()中,我们有三个功能中的两个打破了他们的合同。这不应该告诉我们做不同的事情吗?

这里的主要工作是通过累加器功能完成。要实现折叠式缩减,我们可以使用forEachOrdered()以严格的从左到右的顺序应用此函数。我们必须在之前和之后做一些设置和完成代码,但这没有问题:

StringBuilder a = new StringBuilder();
list.parallelStream()
    .forEachOrdered(b -> a.append(a.toString()).append(b));
System.out.println(a.toString());

当然,并行执行这个工作很好,尽管并行运行的性能优势可能会因为forEachOrdered()的排序要求而被否定。

总之,如果你发现自己想做一个可变的减少,但是你缺乏一个关联的组合器功能,导致你限制你的流顺序执行,把这个问题重写为左手操作,并在你的累加器上使用forEachRemaining()功能。

http://stackoverflow.com/questions/29210176/can-a-collectors-combiner-function-ever-be-used-on-sequential-streams

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:java – 可以在序列流上使用收集器的组合器函数吗?