流操作应用于列表元素的顺序是什么?

假设我们有一个标准的流操作方法链:

Arrays.asList("a", "bc", "def").stream() .filter(e -> e.length() != 2) .map(e -> e.length()) .forEach(e -> System.out.println(e)); 

JLS中是否有关于流操作应用于列表元素的顺序的保证?

例如,是否保证:

  1. 在将过滤谓词应用于"a"之前,不会将过滤谓词应用于"bc" "a"
  2. 在将映射函数应用于"a"之前,不会将映射函数应用于"def" "a"
  3. 1将在3之前打印?

注意 :我在这里特别谈论stream()而不是 parallelStream() ,其中预期映射和过滤等操作是并行完成的。

您可以在java.util.stream JavaDoc中找到您想知道的所有内容。

订购

流可能有也可能没有已定义的遭遇顺序。 流是否具有遭遇顺序取决于源和中间操作。 某些流源(例如List或数组)本质上是有序的,而其他流(例如HashSet)则不是。 某些中间操作(例如sorted())可能会在其他无序流上强制执行遭遇顺序,而其他中间操作可能会呈现无序的有序流,例如BaseStream.unordered()。 此外,一些终端操作可以忽略遭遇顺序,例如forEach()。

如果订购了流,则大多数操作都被约束为对其遭遇顺序中的元素进行操作; 如果流的源是包含[1,2,3]的List,那么执行map(x – > x * 2)的结果必须是[2,4,6]。 但是,如果源没有定义的遭遇顺序,那么值[2,4,6]的任何排列都将是有效的结果。

对于顺序流,遭遇顺序的存在与否不会影响性能,只影响确定性。 如果订购了流,则在相同的源上重复执行相同的流管道将产生相同的结果; 如果没有订购,重复执行可能会产生不同的结果。

对于并行流,放宽排序约束有时可以实现更高效的执行。 如果元素的排序不相关,则可以更有效地实现某些聚合操作,例如过滤重复(distinct())或分组缩减(Collectors.groupingBy())。 类似地,与遇到订单本质上相关的操作(例如limit())可能需要缓冲以确保正确排序,从而破坏并行性的好处。 在流具有遭遇顺序但用户不特别关心该遭遇顺序的情况下,使用无序()明确地对流进行排序可以改善某些有状态或终端操作的并行性能。 然而,大多数流管道,例如上面的“块的权重总和”示例,即使在排序约束下仍然有效地并行化。

JLS中是否有关于流操作应用于列表元素的顺序的保证?

JLS不涵盖Streams库。 您需要阅读库的Javadoc。

Streams还支持并行流,处理事物的顺序取决于实现。

在将过滤谓词应用于“a”之前,不会将过滤谓词应用于“bc”?

可以合理地假设它会,但你不能保证它,也不应该编写需要这种保证的代码,否则你将无法在以后并行化。

在将映射函数应用于“a”之前,不会将映射函数应用于“def”?

假设确实发生这种情况是安全的,但是你不应该编写需要它的代码。

无法保证列表项传递给谓词lambdas的顺序。 流文档可以保证流的输出,包括遇到的顺序; 它不保证实现细节,例如应用过滤谓词的顺序。

因此,文档不会阻止filter ,例如,读取多个元素,以相反的顺序在它们上运行谓词,然后将传递谓词的元素按照它们进入的顺序发送到流的输出。我不要不知道为什么filter()会做那样的事情,但这样做不会破坏文档中的任何保证。

您可以从文档中做出非常强大的推论,即filter()将按照集合提供它们的顺序对元素调用谓词,因为您将调用stream()的结果传递给列表,该列表调用Collection.stream() ,并且,根据Java文档,保证以这种方式生成的Stream顺序的

返回以此集合为源的顺序Stream

此外, filter()无状态的

无状态操作(例如filtermap在处理新元素时不保留先前看到的元素的状态 – 每个元素都可以独立于其他元素上的操作进行处理。

因此, filter很可能会按照集合提供的顺序调用元素上的谓词。

我在这里特别谈论stream() ,而不是parallelStream()

请注意, Stream可能是无序的而不是并行的。 例如,在stream() unordered()上调用unordered() ,结果变为无序,但不是并行。

JLS中是否有关于流操作应用于列表元素的顺序的保证?

从Stream javadocs中的Ordering部分引用

  • 流可能有也可能没有已定义的遭遇顺序。 流是否具有遭遇顺序取决于源和中间操作。


在将过滤谓词应用于“a”之前,不会将过滤谓词应用于“bc”?

如上所述,流可能有也可能没有已定义的顺序。 但是在你的例子中,因为它是一个List, Stream javadocs中的相同Ordering部分继续说明

  • 如果订购了流,则大多数操作都被约束为对其遭遇顺序中的元素进行操作; 如果流的源是包含[1,2,3]的List,那么执行map(x – > x * 2)的结果必须是[2,4,6]。

    将上述语句应用于您的示例 – 我相信,过滤谓词将按照List中定义的顺序接收元素。


或者,在将映射函数应用于“a”之前,不会将映射函数应用于“def”?

为此,我将参考Streams中的Stream操作部分Stream操作 ,

  • 无状态操作(例如filter和映射)在处理新元素时不会保留先前看到的元素的状态

    由于map()不保留状态,我相信,假设在您的示例中“a”之前不会处理“def”是安全的。


1将在3之前打印?

尽管顺序流(如List)可能不太可能,但由于Stream javadocs中的Ordering部分确实指示了

  • 某些终端操作可能会忽略遭遇顺序,例如forEach()。

如果从列表创建流,则保证收集的结果将按原始列表的相同方式排序, 如文档所述 :

订购

如果订购了流,则大多数操作都被约束为对其遭遇顺序中的元素进行操作; 如果流的源是包含[1,2,3]的List,那么执行map(x – > x * 2)的结果必须是[2,4,6]。 但是,如果源没有定义的遭遇顺序,那么值[2,4,6]的任何排列都将是有效的结果。

为了更进一步,不能保证执行map执行的顺序。

从同一文档页面(在副作用段落中):

副作用

如果行为参数确实有副作用,除非明确说明,否则不能保证这些副作用对其他线程的可见性,也不保证在同一个流管道中对“相同”元素的不同操作在同一个线程中执行。 此外,这些效果的排序可能令人惊讶。 即使管道被约束产生的结果与流源的遭遇顺序一致(例如,IntStream.range(0,5).parallel()。map(x – > x * 2).toArray( )必须产生[0,2,4,6,8]),不保证映射器函数应用于单个元素的顺序,或者在给定元素执行任何行为参数的线程中。

实际上,对于有序的顺序流,可能会按顺序执行流操作,但不能保证。