每个有状态中间流 API 操作是否保证新的源集合?



以下陈述是正确的吗?

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

(来源和来源 - 它们似乎相互复制或来自同一来源。

免责声明:我知道以下代码片段不是Java Stream API的合法用法。不要在生产代码中使用。

我已经将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编写了正确的操作,则不会注意到此方案的操作之间的差异。

最新更新