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

Nik*_*las 29 java java-8 java-stream

以下陈述是真的吗?

(来源来源 - 他们似乎互相复制或来自同一来源.)

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]
Run Code Online (Sandbox Code Playgroud)

有用.我替换Stream::sortedStream::distinct,Stream::limitStream::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
Run Code Online (Sandbox Code Playgroud)

令我惊讶的NullPointerException是,它被扔了.

所有测试方法都遵循有状态中间操作特性.然而,Stream::sorted没有记录这种独特的行为,Stream操作和管道部分解释了有状态中间操作是否真正保证了新的源集合.

我的困惑来自何处以及上述行为的解释是什么?

Hol*_*ger 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);
Run Code Online (Sandbox Code Playgroud)

抛出一个ConcurrentModificationException.原因是实现优化了这种情况,因为源已经排序.原则上,它可以对原始示例forEach执行相同的优化,因为显式执行没有指定顺序的操作,因此,排序是不必要的.

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

因此,最重要的是,当依赖于未指明的行为时,今天可能发生的事情,明天可能会在新增优化时加入.

  • @Nikolas好吧,那个博客专注于观察到的行为并查看代码,它应该引用规范.但至少结论基本上是正确的:"*我们只能建议您在使用流*时实际上从不修改支持集合". (10认同)

Eug*_*ene 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 
Run Code Online (Sandbox Code Playgroud)

当然,sorted需要成为一个完整的障碍并停下来进行整个排序,当然,除非它可以被跳过,因此文档没有做出这样的承诺,因此我们不会遇到奇怪的意外.

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