Java 8 – 转换列表的最佳方式:map或foreach?

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

对于Java 8,我注意到我可以用两种不同的方法来完成。 我想知道他们之间更有效的方式,并理解为什么一种方式比另一种更好。

任何有关第三种方式的build议我都会公开。

方法1:

 myListToParse.stream() .filter(elt -> elt != null) .forEach(elt -> myFinalList.add(doSomething(elt))); 

方法2:

 myFinalList = myListToParse.stream() .filter(elt -> elt != null) .map(elt -> doSomething(elt)) .collect(Collectors.toList()); 

不要担心任何性能差异,在这种情况下,它们通常是最小的。

方法2是优选的,因为

  1. 它不需要改变lambdaexpression式之外的集合,

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

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

我同意现有的答案,第二种forms更好,因为它没有任何副作用,并且更容易并行(只使用并行stream)。

性能上看来,直到开始使用并行数据stream时,它们才是等价的。 在这种情况下, 地图的性能会更好。 看下面的微观基准testing结果:

 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 

你不能以相同的方式提升第一个例子,因为forEach是一个terminal方法 – 它返回void – 所以你不得不使用一个有状态的lambda。 但是,如果您使用并行stream,那真是一个糟糕的主意 。

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

 myFinalList = myListToParse.stream() .filter(Objects::nonNull) .map(this::doSomething) .collect(toList()); 

使用stream的主要好处之一是它能够以声明的方式处理数据,也就是使用函数式的编程风格。 它还提供了multithreadingfunction,免费意味着不需要编写任何额外的multithreading代码来使您的stream并发。

假设你正在探索这种编程风格的原因是你想利用这些好处,那么你的第一个代码示例可能不起作用,因为foreach方法被归类为terminal(意味着它可能产生副作用)。

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

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

第二种方法的另一个好处是,如果数据stream是并行的而且收集器是并行的而且是无序的,那么这些特性可以为并行操作的收集操作提供有用的提示。

如果您使用Eclipse集合 ,则可以使用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); 

它急切地评估,应该比使用Stream快一点。

注意:我是Eclipse集合的提交者。

我更喜欢第二种方式。

在使用第一种方法时,如果决定使用并行stream来提高性能,则无法控制forEach将元素添加到输出列表的顺序。

当您使用toList ,即使您使用并行stream,Streams API也将保留顺序。

还有第三种select – 使用stream().toArray() – 查看为什么没有stream有一个toList方法的评论 。 事实certificate,它比forEach()或collect()慢,performance力较差。 它可能会在以后的JDK版本中进行优化,所以在这里添加它以防万一。

假定List<String>

  myFinalList = Arrays.asList( myListToParse.stream() .filter(Objects::nonNull) .map(this::doSomething) .toArray(String[]::new) ); 

在微型基准testing中,1M条目,20%的空值以及doSomething()中的简单转换

 private LongSummaryStatistics benchmark(final String testName, final Runnable methodToTest, int samples) { long[] timing = new long[samples]; for (int i = 0; i < samples; i++) { long start = System.currentTimeMillis(); methodToTest.run(); timing[i] = System.currentTimeMillis() - start; } final LongSummaryStatistics stats = Arrays.stream(timing).summaryStatistics(); System.out.println(testName + ": " + stats); return stats; } 

结果是

平行:

 toArray: LongSummaryStatistics{count=10, sum=3721, min=321, average=372,100000, max=535} forEach: LongSummaryStatistics{count=10, sum=3502, min=249, average=350,200000, max=389} collect: LongSummaryStatistics{count=10, sum=3325, min=265, average=332,500000, max=368} 

顺序:

 toArray: LongSummaryStatistics{count=10, sum=5493, min=517, average=549,300000, max=569} forEach: LongSummaryStatistics{count=10, sum=5316, min=427, average=531,600000, max=571} collect: LongSummaryStatistics{count=10, sum=5380, min=444, average=538,000000, max=557} 

并行没有空和filter(所以stream是SIZED ):toArrays在这种情况下具有最好的性能, .forEach()失败与收件人ArrayList“indexOutOfBounds”,必须replace.forEachOrdered()

 toArray: LongSummaryStatistics{count=100, sum=75566, min=707, average=755,660000, max=1107} forEach: LongSummaryStatistics{count=100, sum=115802, min=992, average=1158,020000, max=1254} collect: LongSummaryStatistics{count=100, sum=88415, min=732, average=884,150000, max=1014} 

可能是方法3。

我总是喜欢保持逻辑分离。

 Predicate<Long> greaterThan100 = new Predicate<Long>() { @Override public boolean test(Long currentParameter) { return currentParameter > 100; } }; List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L); List<Long> resultList = sourceLongList.parallelStream().filter(greaterThan100).collect(Collectors.toList()); 

如果使用3rd Pary Libaries可以,cyclops -react定义了Lazy扩展集合,这个function是内置的。例如,我们可以简单地写

ListX myListToParse;

ListX myFinalList = myListToParse.filter(elt – > elt!= null).map(elt – > doSomething(elt));

myFinalList在第一次访问之前不会被评估(在物化列表被caching和重用之后)。

[披露我是独眼巨人反应的主要开发者]