代码之家  ›  专栏  ›  技术社区  ›  Nikolas Charalambidis

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

  •  28
  • Nikolas Charalambidis  · 技术社区  · 7 年前

    以下陈述是真的吗?

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

    这个 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::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 operations and pipelines 部分解释了 有状态中间操作 真的可以保证一个新的源集合。

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

    3 回复  |  直到 7 年前
        1
  •  30
  •   Holger    7 年前

    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() 可以转换为_查找最小_操作,而无需将元素复制到新存储中进行排序。

    因此,底线是,当依赖于未指定的行为时,今天可能发生的事情,明天可能会打破,当添加新的优化时。

        2
  •  7
  •   Eugene    7 年前

    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 
    

    当然, 排序的 需要成为一个完整的屏障,停止做一个完整的排序,除非,当然,它可以被跳过,因此文档没有作出这样的承诺,这样我们就不会在奇怪的惊喜中运行。

    distinct 另一方面,没有 必须是一个完整的屏障 ,所有distinct都是一次检查一个元素(如果它是唯一的);因此,在检查单个元素(并且它是唯一的)之后,它将被传递到下一个阶段,因此不会成为完整的屏障。不管怎样,这也没有记录在案…

        3
  •  3
  •   Andrew    7 年前

    你不应该带着终端操作提起这些案子 forEach(list::remove) 因为 list::remove 是一个干扰函数,它违反了 "non-interference" 终端动作原理。

    在想一个不正确的代码片段为什么会导致意外的(或未记录的)行为之前,遵循这些规则是至关重要的。

    我相信 名单:删除 是问题的根源。如果编写了适当的操作 forEach .