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

时间:2016-03-19 14:29:58

标签: java java-8 java-stream

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

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

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

例如,是否保证:

  1. 在将过滤谓词应用于"bc"之前,不会将过滤谓词应用于"a"
  2. 在将映射函数应用于"def"之前,不会将映射函数应用于"a"
  3. 1将在3之前打印?
  4. 注意:我在这里专门谈论stream() parallelStream(),其中预计映射和过滤等操作都在平行。

5 个答案:

答案 0 :(得分:9)

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

  

订购

     

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

     

如果订购了流,则大多数操作都被限制为可以进行操作   遇到顺序中的元素; 如果流的来源是a   列表包含[1,2,3],然后执行map的结果(x - > x * 2)   必须是[2,4,6]。但是,如果源没有定义的遭遇   顺序,那么值[2,4,6]的任何排列都是有效的   结果

     

对于顺序流,是否存在遭遇订单   不影响表现,只影响决定论。如果订购了流,   在相同的流程上重复执行相同的流管道   源将产生相同的结果;如果没有订购,   重复执行可能会产生不同的结果。

     

对于并行流,有时可以放宽排序约束   实现更高效的执行。某些聚合操作,例如   过滤重复(distinct())或分组缩减   (Collectors.groupingBy())可以更有效地实现   元素的排序是不相关的。同样,操作也是如此   本质上与遇到订单相关联,例如limit(),可能需要   缓冲以确保正确的订购,从而破坏了利益   并行性。如果流有遭遇订单,但是   用户并不特别关心那次遭遇订单   使用无序()对流进行排序可以改善并行性   某些有状态或终端操作的性能。但是,大多数   流管道,例如块的权重总和"上面的例子,   即使在排序约束下,仍然可以高效并行化。

答案 1 :(得分:4)

  

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

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

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

  

将过滤谓词应用于" bc"在将过滤谓词应用于" a"?

之前不会发生

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

  

将映射函数应用于" def"在将映射函数应用于" a"?

之前不会发生

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

答案 2 :(得分:1)

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

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

您可以从filter()将按照集合提供的顺序对元素调用谓词的文档进行非常强大的推断,因为您在列表中传递了调用stream()的结果,调用Collection.stream(),并根据Java文档,保证以这种方式生成的Stream<T> 顺序

  

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

此外,filter() 无状态

  

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

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

  

我在这里专门谈论stream(),而不是parallelStream()

请注意,Stream<T>可能无序并行。例如,在stream()上调用unordered(),结果会变得无序,但不会并行。

答案 3 :(得分:0)

如果从列表中创建了流,则可以保证收集的结果的排序方式与原始列表的排序方式相同,as the documentation states

  

订购

     

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

为了更进一步,我们无法保证执行map执行的顺序。

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

  

副作用

     

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

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

答案 4 :(得分:0)

  

JLS是否有关于订单的保证   流操作是否应用于列表元素?

引自Ordering section in Stream javadocs

  •   

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

  

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

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

  •   

    如果订购了流,则大多数操作都被限制为可以进行操作   他们遭遇顺序中的元素;如果流的来源是   列表包含[1,2,3],然后执行map的结果(x - &gt; x * 2)   必须是[2,4,6]。

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

  

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

为此,我将参考流操作部分Stream operations in Streams,即

  •   

    无状态操作(例如过滤器和地图)不保留任何状态   处理新元素时先前看过的元素

    由于<ItemsControl ...> <ItemsControl.Template> <ControlTemplate TargetType="ItemsControl"> <Grid> ... <ItemsPresenter/> <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding SomeTextFromVM}"/> </Grid> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Grid> ... </Grid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> ... </ItemsControl.ItemTemplate> <ItemsControl.ItemContainerStyle> ... </ItemsControl.ItemContainerStyle> </ItemsControl> 不保留状态,我相信,可以安全地假设在您的示例中“a”之前不会处理“def”。

  

1将在3之前打印?

尽管顺序流(如List)可能不太可能,但由于Ordering section in Stream javadocs确实表明

,因此无法保证
  •   

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