Collection.stream()。forEach()とCollection.forEach()の違いは何ですか?


286

.stream().filter()並列ストリームのようなチェーン操作を使用できることを理解しています。しかし、小さな操作(たとえば、リストの要素の印刷)を実行する必要がある場合、それらの違いは何ですか?

collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);

回答:


287

図のような単純なケースでは、ほとんど同じです。ただし、重要である可能性がある微妙な違いがいくつかあります。

1つの問題は注文にあります。ではStream.forEach、順序がされて未定義。シーケンシャルストリームで発生する可能性は低いですが、それでも、Stream.forEach任意の順序で実行することは仕様の範囲内です。これは、並列ストリームで頻繁に発生します。対照的に、Iterable.forEachIterable、指定されている場合、常にの反復順序で実行されます。

別の問題は副作用にあります。で指定されてStream.forEachいるアクションは、非干渉である必要があります。(java.util.streamパッケージdocを参照してください。)Iterable.forEach制限が少ない可能性があります。内のコレクションのためにjava.utilIterable.forEach一般的にそのコレクションの使用されますIteratorれるように設計されているほとんどが、高速の失敗とスローされますどのConcurrentModificationExceptionコレクションは構造的に反復中に変更された場合を。しかし、構造的でない変更がされている反復中に許可されます。たとえば、ArrayListクラスのドキュメントには、「単に要素の値を設定するだけでは構造が変更されない」と記載されています。したがって、ArrayList.forEachArrayList問題なく基になる値を設定できます。

並行コレクションはまた異なります。フェイルファストではなく、一貫性弱いように設計されてます。完全な定義はそのリンクにあります。ただし、簡単に説明しますConcurrentLinkedDeque。そのforEachメソッドに渡されたアクションは、構造的にも、基になる両端キューを変更すること許可されており、ConcurrentModificationExceptionスローされることはありません。ただし、発生する変更は、この反復で表示される場合と表示されない場合があります。(したがって、「弱い」一貫性です。)

Iterable.forEach同期されたコレクションを繰り返し処理する場合、さらに別の違いが表示されます。そのようなコレクションでIterable.forEach は、コレクションのロックを一度取得し、アクションメソッドへのすべての呼び出しにわたってロックを保持します。Stream.forEachコールはロックしないコレクションのspliteratorを使用し、非干渉の現行ルールに依存しています。ストリームをバッキングするコレクションは、反復中に変更される可能性があり、変更されている場合は、ConcurrentModificationException動作または一貫性のない動作が発生する可能性があります。


Iterable.forEach takes the collection's lock。この情報はどこから来ましたか?JDKソースでそのような動作を見つけることができません。
ターバノフ2015


@Stuart、非干渉について詳しく説明できますか。Stream.forEach()もConcurrentModificationExceptionをスローします(少なくとも私にとって)。
yuranos

1
@ yuranos87などの多くのコレクションはArrayList、同時変更をかなり厳密にチェックするため、しばしばスローされConcurrentModificationExceptionます。ただし、これは特に並列ストリームの場合は保証されません。CMEの代わりに、予期しない答えが返される場合があります。ストリームソースの非構造的な変更も検討してください。並列ストリームの場合、特定の要素を処理するスレッドや、変更時に処理されているかどうかはわかりません。これにより、実行ごとに異なる結果が得られ、CMEが得られない可能性のある競合状態が設定されます。
スチュアートマークス

30

この答えは、ループのさまざまな実装のパフォーマンスに関係しています。VERY OFTENと呼ばれるループにわずかに関連します(数百万の呼び出しのように)。ほとんどの場合、ループの内容は断然最も高価な要素です。本当に頻繁にループする状況では、これはまだ興味深いかもしれません。

これは実装固有であるため、完全なソースコードであるため、ターゲットシステムでこのテストを繰り返す必要があります

高速Linuxマシンでopenjdkバージョン1.8.0_111を実行しています。

integers(10 ^ 0-> 10 ^ 5エントリ)のさまざまなサイズのこのコードを使用して、リストを10 ^ 6回ループするテストを作成しました。

結果は以下のとおりです。最速の方法は、リスト内のエントリの数によって異なります。

ただし、依然として最悪の状況では、10 ^ 5エントリを10 ^ 6回ループすると、最悪のパフォーマーでは100秒かかりました。そのため、事実上すべての状況で他の考慮事項がより重要になります。

public int outside = 0;

private void forCounter(List<Integer> integers) {
    for(int ii = 0; ii < integers.size(); ii++) {
        Integer next = integers.get(ii);
        outside = next*next;
    }
}

private void forEach(List<Integer> integers) {
    for(Integer next : integers) {
        outside = next * next;
    }
}

private void iteratorForEach(List<Integer> integers) {
    integers.forEach((ii) -> {
        outside = ii*ii;
    });
}
private void iteratorStream(List<Integer> integers) {
    integers.stream().forEach((ii) -> {
        outside = ii*ii;
    });
}

これが私のタイミングです:ミリ秒/関数/リスト内のエントリ数。各実行は10 ^ 6ループです。

                           1    10    100    1000    10000
       iterator.forEach   27   116    959    8832    88958
               for:each   53   171   1262   11164   111005
         for with index   39   112    920    8577    89212
iterable.stream.forEach  255   324   1030    8519    88419

実験を繰り返す場合は、完全なソースコードを投稿しました。この回答を編集して、テストしたシステムの表記で結果を追加してください。


MacBook Pro、2.5 GHz Intel Core i7、16 GB、macOS 10.12.6を使用:

                           1    10    100    1000    10000
       iterator.forEach   27   106   1047    8516    88044
               for:each   46   143   1182   10548   101925
         for with index   49   145    887    7614    81130
iterable.stream.forEach  393   397   1108    8908    88361

Java 8 Hotspot VM-3.4 GHz Intel Xeon、8 GB、Windows 10 Pro

                            1    10    100    1000    10000
        iterator.forEach   30   115    928    8384    85911
                for:each   40   125   1166   10804   108006
          for with index   30   120    956    8247    81116
 iterable.stream.forEach  260   237   1020    8401    84883

Java 11 Hotspot VM-3.4 GHz Intel Xeon、8 GB、Windows 10 Pro
(上記と同じマシン、異なるJDKバージョン)

                            1    10    100    1000    10000
        iterator.forEach   20   104    940    8350    88918
                for:each   50   140    991    8497    89873
          for with index   37   140    945    8646    90402
 iterable.stream.forEach  200   270   1054    8558    87449

Java 11 OpenJ9 VM-3.4 GHz Intel Xeon、8 GB、Windows 10 Pro
(上記と同じマシンとJDKバージョン、異なるVM)

                            1    10    100    1000    10000
        iterator.forEach  211   475   3499   33631   336108
                for:each  200   375   2793   27249   272590
          for with index  384   467   2718   26036   261408
 iterable.stream.forEach  515   714   3096   26320   262786

Java 8ホットスポットVM-2.8 GHz AMD、64 GB、Windows Server 2016

                            1    10    100    1000    10000
        iterator.forEach   95   192   2076   19269   198519
                for:each  157   224   2492   25466   248494
          for with index  140   368   2084   22294   207092
 iterable.stream.forEach  946   687   2206   21697   238457

Java 11 Hotspot VM-2.8 GHz AMD、64 GB、Windows Server 2016
(上記と同じマシン、異なるJDKバージョン)

                            1    10    100    1000    10000
        iterator.forEach   72   269   1972   23157   229445
                for:each  192   376   2114   24389   233544
          for with index  165   424   2123   20853   220356
 iterable.stream.forEach  921   660   2194   23840   204817

Java 11 OpenJ9 VM-2.8 GHz AMD、64 GB、Windows Server 2016
(上記と同じマシンとJDKバージョン、異なるVM)

                            1    10    100    1000    10000
        iterator.forEach  592   914   7232   59062   529497
                for:each  477  1576  14706  129724  1190001
          for with index  893   838   7265   74045   842927
 iterable.stream.forEach 1359  1782  11869  104427   958584

選択したVMの実装によっても、ホットスポット/ OpenJ9などが異なります。


3
それはとても良い答えです、ありがとう!しかし、一見しただけでは(また2番目からも)、どの方法がどの実験に対応するかは不明です。
torina

この答えはコードテストにもっと賛成票が必要だと思います:)
コリー

テストの例+1
Centos

8

あなたが言及した2つに違いはありません。少なくとも概念的には、これCollection.forEach()は単なる速記です。

内部的には、stream()バージョンにはオブジェクトの作成のために多少多くのオーバーヘッドがありますが、実行時間を見るとどちらにもオーバーヘッドはありません。

両方の実装では、繰り返し処理を終了しcollection、一度コンテンツ、及び素子アウト反復プリント。


あなたが言及するオブジェクト作成オーバーヘッド、あなたはStream作成されているのか、それとも個々のオブジェクトのことですか?申し訳ありStreamませんが、a は要素を複製しません。
Raffi Khatchadourian、2015年

30
この答えは、Oracle CorporationでJavaコアライブラリを開発する紳士が書いた優れた答えとは矛盾しているようです。
Dawood ibnカリーム2017年

0

Collection.forEach()は、コレクションのイテレータ(指定されている場合)を使用します。つまり、アイテムの処理順序が定義されます。対照的に、Collection.stream()。forEach()の処理順序は定義されていません。

ほとんどの場合、どちらを選択しても違いはありません。並列ストリームを使用すると、複数のスレッドでストリームを実行できます。このような状況では、実行順序は定義されていません。Javaでは、Collectors.toList()などの端末操作が呼び出される前に、すべてのスレッドが終了することだけが必要です。最初にコレクションで直接forEach()を呼び出し、次に並列ストリームで呼び出す例を見てみましょう。

list.forEach(System.out::print);
System.out.print(" ");
list.parallelStream().forEach(System.out::print);

コードを複数回実行すると、list.forEach()が挿入順にアイテムを処理するのに対し、list.parallelStream()。forEach()は実行ごとに異なる結果を生成します。可能な出力の1つは次のとおりです。

ABCD CDBA

もう一つは:

ABCD DBCA
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.