web-dev-qa-db-ja.com

Java 8 - リストを変換する最善の方法:地図かforeachか

私はリストmyListToParseを持っていて、ここで私は要素をフィルタして各要素にメソッドを適用し、そして結果を別のリストmyFinalListに追加します。

Java 8では、2つの方法で実行できることに気付きました。私はそれらの間のより効率的な方法を知り、そして一方の方法が他方の方法より優れている理由を理解したいと思います。

私は3番目の方法についての提案を開いています。

方法1:

myFinalList = new ArrayList<>();
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()); 
164
Emilien Brigand

パフォーマンスの違いを気にする必要はありません。通常、この場合は最小限になります。

方法2が好ましいのは、

  1. ラムダ式の外側に存在するコレクションを変更する必要はありません。

  2. コレクションパイプラインで実行されるさまざまな手順が順番に(最初にフィルター操作、次にマップ操作、そして結果の収集)書き込まれるため、読みやすくなります(コレクションパイプラインの利点の詳細については、Martin Fowler( 優れた記事

  3. 使用されているCollectorを置き換えることで、値の収集方法を簡単に変更できます。場合によっては、独自のCollectorを書く必要があるかもしれませんが、その場合の利点はそれを簡単に再利用できることです。

135
herman

2番目の形式は副作用がなく、並列化が容易であるため、2番目の形式の方が優れているという既存の回答に同意します(単に並列ストリームを使用する)。

パフォーマンス面では、並列ストリームを使い始めるまでは同等のようです。その場合、mapの方がはるかに良いパフォーマンスを発揮します。以下の マイクロベンチマーク の結果を参照してください。

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は端末メソッドであるため、最初の例を同じ方法で拡張することはできません。つまり、voidが返されます。しかし 並列ストリームを使用している場合は これは本当に悪い考えです。

最後に、2番目のスニペットは、メソッド参照と静的インポートを使って、より簡潔に書くことができます。

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

ストリームを使用することの主な利点の1つは、宣言的な方法で、つまり関数型のプログラミングを使用してデータを処理できることです。それはまたあなたのストリームを並行にするために特別なマルチスレッドコードを書く必要がないという自由な意味のためのマルチスレッド機能を与えます。

このスタイルのプログラミングを検討している理由は、これらの利点を活用したいということであると仮定すると、最初のコードサンプルは潜在的に機能しません(foreachメソッドは終了として分類されます)。

2番目の方法は、マップ関数がステートレスラムダ関数を受け入れることができるので、関数型プログラミングの観点からは好まれます。もっと明確に言うと、map関数に渡されるラムダは

  1. 干渉しない、つまり関数がストリームのソースを同時ではない場合には変更してはいけません(例:ArrayList)。
  2. 並列処理を実行するときに予期しない結果を避けるためのステートレス(スレッドスケジューリングの違いによる)。

2番目の方法のもう1つの利点は、ストリームが並列で、コレクターが並行で順序付けされていない場合、これらの特性が、並行して収集を行うための削減操作に役立つヒントを提供することです。

5
M.K.

あなたが 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コレクションのコミッターです。

4
Craig P. Motlin

私は2番目の方法を好みます。

最初の方法を使用するときに、パフォーマンスを向上させるために並列ストリームを使用することにした場合、forEachによって要素が出力リストに追加される順序を制御できません。

toListを使用すると、パラレルストリームを使用している場合でもStreams APIは順序を保持します。

1
Eran

stream().toArray()を使用する - の下のコメントを参照してください。なぜtoListメソッドをストリームできないのか を参照してください。 forEach()やcollect()より遅くなり、表現力が落ちます。将来のJDKビルドで最適化される可能性があるため、念のためここで追加します。

List<String>を仮定する

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

マイクロ・マイクロベンチマーク、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}

nullとフィルタなしの並列処理(したがってストリームはSIZED):そのような場合にtoArraysが最高のパフォーマンスを発揮し、.forEach()は受信側ArrayListの "indexOutOfBounds"で失敗し、.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}
0
harshtuna

方法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());
0
Kumar Abhishek

3rd Pary Librariesを使用しても問題ない場合 cyclops-react は、この機能を組み込んでLazy拡張コレクションを定義します。

ListX myListToParse;

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

myFinalListは、最初のアクセスまで(そしてマテリアライズド・リストがキャッシュに入れられて再利用されるまで)評価されません。

[私はcyclops-reactの主導的開発者です]

0
John McClean