以下陈述正确吗?
sorted()
操作是“有状态的中间操作”,这意味着后续操作不再对后备集合进行操作,而是对内部状态进行操作。
(Source和source-它们似乎是彼此复制或来自同一来源。)
我已经从以上来源测试了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
。
所有经过测试的方法都遵循stateful intermediate operation特性。但是,Stream::sorted
的这种独特行为未得到记录,Stream operations and pipelines部分也没有说明有状态中间操作是否真正保证了新的源集合。
我的困惑来自哪里,上述行为的解释是什么?
答案 0 :(得分:31)
API文档没有做出这样的保证“后续操作将不再对后备集合进行操作”,因此,您永远不应依赖于特定实现的这种行为。
您的榜样偶然发生了想要的事情;甚至不能保证List
创建的collect(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()
可以转换为“查找最小值”操作,而无需将元素复制到新存储中进行排序。
因此,最重要的是,当依靠未指定的行为时,今天可能会发生的事情,明天可能会在添加新的优化措施时中断。
答案 1 :(得分:7)
好吧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
不必完全成为障碍,唯一不同的是一次检查一个元素(如果唯一);因此,在检查了一个元素(并且它是唯一的)之后,该元素将传递到下一阶段,因此不会成为一个完整的障碍。无论哪种方式,这也都没有记录...
答案 2 :(得分:3)
您不应该使用终端操作forEach(list::remove)
来提起诉讼,因为list::remove
是一种干扰功能,并且违反了"non-interference"的终端操作原则。
在怀疑为什么不正确的代码片段会导致意外(或未记录的)行为之前,务必遵循规则。
我相信list::remove
是这里问题的根源。如果您为forEach
编写了适当的操作,则不会注意到此方案的操作之间的差异。