为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

拉丁的传说 2021-09-12 20:17:52
下面的说法是真的吗?该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::sorted为Stream::distinct,Stream::limit和Stream::skip: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没有记录,流操作和管道部分也没有解释有状态的中间操作是否真的保证了新的源集合。我的困惑来自哪里,对上述行为的解释是什么?
查看完整描述

3 回答

?
SMILET

TA贡献1796条经验 获得超4个赞

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


你的例子碰巧不小心做了想要的事情;甚至不能保证Listcreated bycollect(Collectors.toList())支持该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()可以转换为“找到最小值”操作,而无需将元素复制到新的存储中进行排序。


所以底线是,当依赖未指定的行为时,今天可能发生的事情,明天可能会在添加新的优化时崩溃。


查看完整回答
反对 回复 2021-09-12
?
www说

TA贡献1775条经验 获得超8个赞

Wellsorted必须是流管道的完整复制屏障,毕竟您的源可能无法排序;但这并没有记录在案,因此不要依赖它。


这不仅仅是关于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另一方面,不必是完整的屏障,所有不同的只是一次检查一个元素,如果它是唯一的;所以在检查单个元素(并且它是唯一的)之后,它会被传递到下一个阶段,因此不会成为一个完整的障碍。无论哪种方式,这也没有记录......


查看完整回答
反对 回复 2021-09-12
?
ibeautiful

TA贡献1993条经验 获得超5个赞

您不应该使用终端操作提出案例,forEach(list::remove)因为它list::remove是一种干扰功能,并且违反了终端操作的“不干扰”原则。

在想知道为什么不正确的代码片段会导致意外(或未记录)的行为之前,遵守规则至关重要。

我相信这list::remove是问题的根源。如果您为forEach.


查看完整回答
反对 回复 2021-09-12
  • 3 回答
  • 0 关注
  • 162 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信