猿问

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

下面的说法是真的吗?

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没有记录,流操作和管道部分也没有解释有状态的中间操作是否真的保证了新的源集合。


我的困惑来自哪里,对上述行为的解释是什么?


拉丁的传说
浏览 163回答 3
3回答

SMILET

API 文档不保证“后续操作不再对后备集合进行操作”,因此,您永远不应该依赖特定实现的这种行为。你的例子碰巧不小心做了想要的事情;甚至不能保证Listcreated bycollect(Collectors.toList())支持该remove操作。展示一个反例Set<Integer> set = IntStream.range(0, 10).boxed()&nbsp; &nbsp; .collect(Collectors.toCollection(TreeSet::new));set.stream()&nbsp; &nbsp; .filter(i -> i > 5)&nbsp; &nbsp; .sorted()&nbsp; &nbsp; .forEach(set::remove);抛出一个ConcurrentModificationException. 原因是实现优化了这个场景,因为源已经排序。原则上,它可以对您的原始示例进行相同的优化,因为forEach没有指定的顺序显式执行操作,因此,排序是不必要的。还有其他可以想象的优化,例如sorted().findFirst()可以转换为“找到最小值”操作,而无需将元素复制到新的存储中进行排序。所以底线是,当依赖未指定的行为时,今天可能发生的事情,明天可能会在添加新的优化时崩溃。

www说

Wellsorted必须是流管道的完整复制屏障,毕竟您的源可能无法排序;但这并没有记录在案,因此不要依赖它。这不仅仅是关于sorted本身,而是可以对流管道进行哪些其他优化,因此sorted可以完全跳过。例如:List<Integer> sortedList = IntStream.range(0, 10)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .boxed()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .collect(Collectors.toList());&nbsp; &nbsp; StreamSupport.stream(() -> sortedList.spliterator(), Spliterator.SORTED, false)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .sorted()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .forEach(sortedList::remove); // fails with CME, thus no copying occurred&nbsp;当然,sorted需要是一个完整的屏障并停止做一个完整的排序,除非,当然,它可以被跳过,因此文档没有做出这样的承诺,所以我们不会在奇怪的惊喜中运行。distinct另一方面,不必是完整的屏障,所有不同的只是一次检查一个元素,如果它是唯一的;所以在检查单个元素(并且它是唯一的)之后,它会被传递到下一个阶段,因此不会成为一个完整的障碍。无论哪种方式,这也没有记录......

ibeautiful

您不应该使用终端操作提出案例,forEach(list::remove)因为它list::remove是一种干扰功能,并且违反了终端操作的“不干扰”原则。在想知道为什么不正确的代码片段会导致意外(或未记录)的行为之前,遵守规则至关重要。我相信这list::remove是问题的根源。如果您为forEach.
随时随地看视频慕课网APP

相关分类

Java
我要回答