web-dev-qa-db-ja.com

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

.stream()を使えば、.filter()のようなチェーン操作やパラレルストリームを使うことができます。しかし、私が小さな操作(例えば、リストの要素を印刷すること)を実行する必要があるならば、それらの間の違いは何ですか?

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

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

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

別の問題は副作用にあります。 Stream.forEachで指定されたアクションは、非干渉である必要があります。 ( Java.util.streamパッケージのドキュメント を参照してください。)Iterable.forEachには制限が少ない可能性があります。 Java.util内のコレクションの場合、Iterable.forEachは通常そのコレクションのIteratorを使用します。そのほとんどは fail-fast になるように設計されており、コレクションが反復中に構造的に変更されるとConcurrentModificationExceptionをスローします。ただし、構造的でない修正は、反復中に許可されます。たとえば、 ArrayListクラスのドキュメント によると、単に要素の値を設定するだけでは構造上の変更は行われません。したがって、ArrayList.forEachに対するアクションは、基礎となるArrayListに問題なく値を設定することができます。

同時収集はまだ異なります。フェイルファストの代わりに、それらは 弱い一貫性のある になるように設計されています。完全な定義はそのリンクにあります。簡単に言えば、ConcurrentLinkedDequeを考えてください。そのforEachメソッドに渡されたアクションは、基本的な両端キューを構造的にさえ修正することができ、ConcurrentModificationExceptionはスローされません。ただし、発生した変更は、この反復では表示されない場合があります。 (それ故に「弱い」一貫性。)

Iterable.forEachが同期コレクションに対して反復している場合、さらに別の違いが見えます。そのようなコレクションでは、Iterable.forEachコレクションのロックを取得します を1回実行し、それをアクションメソッドへのすべての呼び出しで保持します。 Stream.forEach呼び出しはコレクションのspliteratorを使用します。これはロックせず、一般的な非干渉の規則に依存します。ストリームをバッキングしているコレクションは、反復中に変更される可能性があり、変更されていると、ConcurrentModificationExceptionまたは矛盾する動作が生じる可能性があります。

244
Stuart Marks

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

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

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

integersのサイズを変えて(10 ^ 0 - > 10 ^ 5エントリ)、このコードを使ってList上で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
         for with index   39   112    920    8577    89212
       iterator.forEach   27   116    959    8832    88958
               for:each   53   171   1262   11164   111005
iterable.stream.forEach  255   324   1030    8519    88419

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

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

(MacBook Pro、2.5 GHz Intel Core i7、16 GB、macOS 10.12.6)

16
Angelo Fuchs

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

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

どちらの実装もcollectionの内容を1回反復処理することになり、while反復処理で要素が出力されます。

8
skiwi