.stream()
を使えば、.filter()
のようなチェーン操作やパラレルストリームを使うことができます。しかし、私が小さな操作(例えば、リストの要素を印刷すること)を実行する必要があるならば、それらの間の違いは何ですか?
collection.stream().forEach(System.out::println);
collection.forEach(System.out::println);
図のような単純なケースでは、ほとんど同じです。ただし、重要な違いがいくつかあります。
問題の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
または矛盾する動作が生じる可能性があります。
この答えは、ループのさまざまな実装のパフォーマンスに関するものです。それは非常に頻繁に呼ばれるループにわずかに関連しています(何百万もの呼び出しのように)。ほとんどの場合、ループの内容ははるかに最も高価な要素になります。あなたが本当に頻繁にループする状況では、これはまだ興味深いかもしれません。
これは実装固有であるため、ターゲットシステムでこのテストを繰り返す必要があります( 完全なソースコード )。
私は速い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)
あなたが言及した2つの間に違いはありません、少なくとも概念的に、Collection.forEach()
は単なる略記です。
内部的にはstream()
バージョンはオブジェクト作成のためにいくらかオーバーヘッドがありますが、実行時間を見るとオーバーヘッドはありません。
どちらの実装もcollection
の内容を1回反復処理することになり、while反復処理で要素が出力されます。