Java 8 - Best way to transform a list: map or foreach?

前端 未结 8 1828
隐瞒了意图╮
隐瞒了意图╮ 2020-12-07 07:01

I have a list myListToParse where I want to filter the elements and apply a method on each element, and add the result in another list myFinalList.

相关标签:
8条回答
  • 2020-12-07 07:39

    If you use Eclipse Collections you can use the collectIf() method.

    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);
    

    It evaluates eagerly and should be a bit faster than using a Stream.

    Note: I am a committer for Eclipse Collections.

    0 讨论(0)
  • 2020-12-07 07:41

    I agree with the existing answers that the second form is better because it does not have any side effects and is easier to parallelise (just use a parallel stream).

    Performance wise, it appears they are equivalent until you start using parallel streams. In that case, map will perform really much better. See below the micro benchmark results:

    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
    

    You can't boost the first example in the same manner because forEach is a terminal method - it returns void - so you are forced to use a stateful lambda. But that is really a bad idea if you are using parallel streams.

    Finally note that your second snippet can be written in a sligthly more concise way with method references and static imports:

    myFinalList = myListToParse.stream()
        .filter(Objects::nonNull)
        .map(this::doSomething)
        .collect(toList()); 
    
    0 讨论(0)
  • 2020-12-07 07:47

    Don't worry about any performance differences, they're going to be minimal in this case normally.

    Method 2 is preferable because

    1. it doesn't require mutating a collection that exists outside the lambda expression,

    2. it's more readable because the different steps that are performed in the collection pipeline are written sequentially: first a filter operation, then a map operation, then collecting the result (for more info on the benefits of collection pipelines, see Martin Fowler's excellent article),

    3. you can easily change the way values are collected by replacing the Collector that is used. In some cases you may need to write your own Collector, but then the benefit is that you can easily reuse that.

    0 讨论(0)
  • 2020-12-07 07:50

    I prefer the second way.

    When you use the first way, if you decide to use a parallel stream to improve performance, you'll have no control over the order in which the elements will be added to the output list by forEach.

    When you use toList, the Streams API will preserve the order even if you use a parallel stream.

    0 讨论(0)
  • 2020-12-07 07:51

    If using 3rd Pary Libaries is ok cyclops-react defines Lazy extended collections with this functionality built in. For example we could simply write

    ListX myListToParse;

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

    myFinalList is not evaluated until first access (and there after the materialized list is cached and reused).

    [Disclosure I am the lead developer of cyclops-react]

    0 讨论(0)
  • 2020-12-07 07:53

    One of the main benefits of using streams is that it gives the ability to process data in a declarative way, that is, using a functional style of programming. It also gives multi-threading capability for free meaning there is no need to write any extra multi-threaded code to make your stream concurrent.

    Assuming the reason you are exploring this style of programming is that you want to exploit these benefits then your first code sample is potentially not functional since the foreach method is classed as being terminal (meaning that it can produce side-effects).

    The second way is preferred from functional programming point of view since the map function can accept stateless lambda functions. More explicitly, the lambda passed to the map function should be

    1. Non-interfering, meaning that the function should not alter the source of the stream if it is non-concurrent (e.g. ArrayList).
    2. Stateless to avoid unexpected results when doing parallel processing (caused by thread scheduling differences).

    Another benefit with the second approach is if the stream is parallel and the collector is concurrent and unordered then these characteristics can provide useful hints to the reduction operation to do the collecting concurrently.

    0 讨论(0)
提交回复
热议问题