Java 8 - 转换列表的最佳方法:map或foreach?

Emi*_*and 174 java java-8 java-stream

我有一个列表myListToParse,我想过滤元素并在每个元素上应用一个方法,并将结果添加到另一个列表中myFinalList.

使用Java 8,我注意到我可以通过两种不同的方式完成它.我想知道他们之间更有效的方式,并理解为什么一种方式比另一种更好.

我对任何有关第三种方式的建议持开放态度.

方法1:

myFinalList = new ArrayList<>();
myListToParse.stream()
        .filter(elt -> elt != null)
        .forEach(elt -> myFinalList.add(doSomething(elt)));
Run Code Online (Sandbox Code Playgroud)

方法2:

myFinalList = myListToParse.stream()
        .filter(elt -> elt != null)
        .map(elt -> doSomething(elt))
        .collect(Collectors.toList()); 
Run Code Online (Sandbox Code Playgroud)

her*_*man 144

不要担心任何性能差异,在这种情况下它们通常会很小.

方法2是优选的,因为

  1. 它不需要改变lambda表达式之外的集合,

  2. 它更具可读性,因为在集合管道中执行的不同步骤是按顺序编写的(首先是过滤操作,然后是地图操作,然后收集结果),(有关收集管道优点的更多信息,请参阅Martin Fowler的优秀文章)

  3. 您可以通过替换Collector使用的值来轻松更改值的收集方式.在某些情况下,您可能需要自己编写Collector,但其好处是您可以轻松地重复使用它.


ass*_*ias 41

我同意现有的答案,第二种形式更好,因为它没有任何副作用,更容易并行化(只使用并行流).

性能方面,在您开始使用并行流之前,它们似乎是等效的.在这种情况下,地图将表现得更好.请参阅以下微基准测试结果:

Benchmark                         Mode  Samples    Score   Error  Units
SO28319064.forEach                avgt      100  187.310 ± 1.768  ms/op
SO28319064.map                    avgt      100  189.180 ± 1.692  ms/op
SO28319064.mapWithParallelStream  avgt      100   55,577 ± 0,782  ms/op
Run Code Online (Sandbox Code Playgroud)

你不能以相同的方式提升第一个例子,因为forEach是一个终端方法 - 它返回void - 所以你被迫使用有状态的lambda.但如果您使用并行流,这确实是一个坏主意.

最后请注意,您的第二个代码段可以使用方法引用和静态导入以更简洁的方式编写:

myFinalList = myListToParse.stream()
    .filter(Objects::nonNull)
    .map(this::doSomething)
    .collect(toList()); 
Run Code Online (Sandbox Code Playgroud)

  • @GiuseppeBertone,这取决于assylias,但我认为你的编辑与原作者的意图相矛盾.如果你想添加自己的答案,最好添加它而不是编辑现有的答案.此外,微基准测试的链接与结果无关. (2认同)

M.K*_*.K. 5

使用流的主要好处之一是它提供了以声明方式处理数据的能力,即使用函数式编程.它还提供免费的多线程功能,这意味着无需编写任何额外的多线程代码来使您的流并发.

假设您正在探索这种编程风格的原因是您希望利用这些优势,那么您的第一个代码示例可能无法正常运行,因为该foreach方法被归类为终端(意味着它可以产生副作用).

从功能编程的角度来看,第二种方式是优选的,因为map函数可以接受无状态lambda函数.更明确地说,传递给map函数的lambda应该是

  1. 非干扰,意味着如果流是非并发的(例如ArrayList),则该函数不应该改变流的源.
  2. 无状态以避免在执行并行处理时出现意外结果(由线程调度差异引起).

第二种方法的另一个好处是如果流是并行的并且收集器是并发的和无序的,那么这些特性可以为还原操作提供有用的提示以同时进行收集.


Cra*_*lin 5

如果您使用Eclipse Collections,则可以使用该collectIf()方法。

MutableList<Integer> source =
    Lists.mutable.with(1, null, 2, null, 3, null, 4, null, 5);

MutableList<String> result = source.collectIf(Objects::nonNull, String::valueOf);

Assert.assertEquals(Lists.immutable.with("1", "2", "3", "4", "5"), result);
Run Code Online (Sandbox Code Playgroud)

它急切地求值并且应该比使用 Stream 快一点。

注意:我是 Eclipse Collections 的提交者。