繁体   English   中英

Java 8 Stream API - 任何有状态的中间操作都能保证新的源集合吗?

[英]Java 8 Stream API - Does any stateful intermediate operation guarantee a new source collection?

以下陈述是真的吗?

sorted()操作是“有状态的中间操作”,这意味着后续操作不再对后备集合进行操作,而是对内部状态进行操作。

来源来源 - 他们似乎互相复制或来自同一来源。)

我已经测试了Stream::sorted作为上述来源的片段:

final List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());

list.stream()
    .filter(i -> i > 5)
    .sorted()
    .forEach(list::remove);

System.out.println(list);            // Prints [0, 1, 2, 3, 4, 5]

有用。 我用Stream::distinctStream::limitStream::skip替换了Stream::sorted

final List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());

list.stream()
    .filter(i -> i > 5)
    .distinct()
    .forEach(list::remove);          // Throws NullPointerException

令我惊讶的是,抛出了NullPointerException

所有测试方法都遵循有状态中间操作特性。 然而, Stream::sorted这种独特行为没有记录, Stream操作和管道部分也解释了有状态中间操作是否真正保证了新的源集合。

我的困惑来自何处以及上述行为的解释是什么?

API文档没有保证“后续操作不再对后备集合进行操作”,因此,您永远不应该依赖于特定实现的这种行为。

你的例子偶然发生了想要的事情; 甚至不能保证collect(Collectors.toList())创建的List支持remove操作。

显示一个反例

Set<Integer> set = IntStream.range(0, 10).boxed()
    .collect(Collectors.toCollection(TreeSet::new));
set.stream()
    .filter(i -> i > 5)
    .sorted()
    .forEach(set::remove);

抛出ConcurrentModificationException 原因是实现优化了这种情况,因为源已经排序。 原则上,它可以对原始示例执行相同的优化,因为forEach以无指定顺序显式执行操作,因此,排序是不必要的。

还有其他可以想象的优化,例如sorted().findFirst()可以转换为“查找最小”操作,而无需将元素复制到新存储中进行排序。

因此,最重要的是,当依赖于未指明的行为时,今天可能发生的事情可能会在明天,即添加新的优化时中断。

sorted好后必须是流管道的完整复制屏障,因为所有源都无法排序 ; 但这并没有记录,因此不依赖它。

不仅仅是关于sorted本身,而是可以对流管道进行其他优化,以便可以完全跳过sorted 例如:

List<Integer> sortedList = IntStream.range(0, 10)
            .boxed()
            .collect(Collectors.toList());

    StreamSupport.stream(() -> sortedList.spliterator(), Spliterator.SORTED, false)
            .sorted()
            .forEach(sortedList::remove); // fails with CME, thus no copying occurred 

当然, sorted需要是一个完整的障碍并停止进行整个排序,当然,除非它可以被跳过,因此文档没有做出这样的承诺,因此我们不会遇到奇怪的意外。

distinct另一方面不必是完整的屏障 ,所有不同的作用是在一个时间检查一个元件,如果是唯一的; 因此,在检查单个元素(并且它是唯一的)之后,它将被传递到下一个阶段,因此不会成为完整的障碍。 无论哪种方式,这也没有记录......

你不应该通过forEach(list::remove)的终端操作提起案例,因为list::remove是一个干扰函数,它违反了终端动作的“非干扰”原则。

在了解为什么不正确的代码段导致意外(或未记录)行为之前,遵循规则至关重要。

我相信list::remove是问题的根源。 如果你为forEach写了一个合适的动作,你就不会注意到这个场景的操作之间的区别。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM