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

Java8流API-任何有状态的中间操作都能保证新的源集合吗?,java,java-8,java-stream,Java,Java 8,Java Stream,以下说法正确吗 sorted()操作是一个“有状态的中间操作”,这意味着后续操作不再对备份集合进行操作,而是对内部状态进行操作 (而且——它们似乎相互抄袭或来自同一来源。) 我已作为上述来源的片段进行了测试: final List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList()); list.stream() .filter(i -> i > 5) .s

以下说法正确吗

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]
令我惊讶的是,
NullPointerException
被抛出

所有测试方法都符合这些特点。然而,这种独特的行为并没有被记录下来,该部分也没有解释有状态的中间操作是否真正保证了新的源集合


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

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

你的例子碰巧做了你想要做的事情;甚至不能保证由
collect(Collectors.toList())
创建的
列表
支持
删除
操作

举个反例

Set<Integer> set = IntStream.range(0, 10).boxed()
    .collect(Collectors.toCollection(TreeSet::new));
set.stream()
    .filter(i -> i > 5)
    .sorted()
    .forEach(set::remove);
Set Set=IntStream.range(0,10).boxed()
.collect(收集器.toCollection(TreeSet::new));
set.stream()
.过滤器(i->i>5)
.已排序()
.forEach(set::remove);
抛出一个
ConcurrentModificationException
。原因是实现优化了该场景,因为源已经排序。原则上,它可以对原始示例执行相同的优化,因为
forEach
没有指定顺序显式执行操作,因此排序是不必要的

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


因此,底线是,当依赖于未指定的行为时,今天可能发生的工作,明天可能会中断,当添加新的优化时。

Well
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 
List sortedList=IntStream.range(0,10)
.boxed()
.collect(Collectors.toList());
StreamSupport.stream(()->sortedList.spliterator(),spliterator.SORTED,false)
.已排序()
.forEach(sortedList::remove);//CME失败,因此未发生复制
当然,
sorted
需要是一个完整的屏障,并停下来进行整个排序,当然,除非可以跳过它,因此文档没有做出这样的承诺,这样我们就不会在奇怪的惊喜中运行


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

您不应该提出终端操作的案例
forEach(list::remove)
,因为
list::remove
是一个干扰函数,它违反了终端操作的原则

在了解错误代码段为何会导致意外(或未记录)行为之前,遵循规则是至关重要的


我认为
list::remove
是问题的根源。如果您为
forEach

@Nikolas编写了一个正确的操作,那么您就不会注意到这个场景中操作之间的差异了。好吧,这个博客关注的是观察到的行为并查看代码,而它本应该参考规范。但至少结论基本上是正确的:“我们只能建议您在使用流时,实际上从未修改支持集合”。我知道您答案的第一段,安德鲁。在这里,我将使用迭代器或collect创建一个列表,以将其从源代码中全部删除。第二段很有意义。@Nikolas没有人指出
list::remove
在这里不合适。我想强调一下。这不是对问题的回答,但对于一般的流建议来说:这可能不是最好的例子,因为对于流来说,执行类似于
List newList=List.stream().filter(…).collect(…)
然后从那里使用流的结果(
newList
)。如果将流视为返回结果(无论是修改的还是创建的新结果)的函数,则流更容易理解,您可以对其进行进一步操作,而不是原始源。那么你就不必(在大多数情况下)担心像这样的实现细节了。@Jonaddams:我知道。
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