オラクルのウェブサイト上のドキュメントに従って[...]
このリンクはJava 8向けです。Java9(2017年にリリースされた)以降のバージョンのドキュメントは、この点についてより明確になっているため、読むことをお勧めします。具体的には:
ストリームの実装では、結果の計算を最適化する際にかなりの自由度が許可されています。たとえば、ストリームの実装は、演算(またはステージ全体)をストリームパイプラインから自由に除外できます。したがって、計算の結果に影響を及ぼさないことが証明できれば、動作パラメータの呼び出しを除外できます。これは、特に指定されていない限り(端末の操作forEach
やなどforEachOrdered
)、動作パラメータの副作用が常に実行されるとは限らず、信頼してはならないことを意味します。(そのような最適化の具体例については、count()
操作に記載されているAPIノートを参照してください。詳細については、ストリームパッケージのドキュメントの「副作用」セクションを参照してください。)
出典:インターフェイスのJava 9のJavadocStream
。
また、引用したドキュメントの更新版:
副作用
ストリーム操作に対する動作パラメータの副作用は、一般に推奨されません。これらは、無意識の要件の意図しない違反や、その他のスレッドセーフティの危険につながることが多いためです。
動作パラメータに副作用がある場合、明示的に述べられていない限り、保証はありません。。
- 他のスレッドへのそれらの副作用の可視性;
- 同じストリームパイプライン内の「同じ」要素に対する異なる操作は、同じスレッドで実行されます。そして
- ストリームの実装は、計算の結果に影響を及ぼさないことが証明できる場合、ストリームパイプラインから操作(またはステージ全体)を自由に除外できるため、その動作パラメーターは常に呼び出されます。
副作用の順序は驚くかもしれません。パイプラインは、ストリームソースの出会いの順序と一致する結果を生成するように制約される場合であっても(例えば、IntStream.range(0,5).parallel().map(x -> x*2).toArray()
製造しなければなりません[0, 2, 4, 6, 8]
)、マッパー関数が個々の要素に適用される順序、または特定の要素に対して実行される動作パラメータのスレッド。
副作用を排除することも驚くべきことかもしれません。端末操作forEach
とを除いてforEachOrdered
、ストリームの実装が計算の結果に影響を与えずに動作パラメータの実行を最適化できる場合、動作パラメータの副作用が常に実行されるとは限りません。(特定の例については、count
操作に。)
ソース:パッケージのJava 9のJavadocjava.util.stream
。
すべての強調は私のものです。
ご覧のとおり、現在の公式ドキュメントでは、ストリーム操作で副作用を使用することにした場合に発生する可能性がある問題について詳しく説明しています。また、上の非常に明確であるforEach
とforEachOrdered
副作用の実行が保証されているのみで、端末の操作を(公式の例が示すように、スレッドの安全性の問題はまだ適用され、あなたを気にし)ています。
それが言われていて、あなたの特定のコードに関して、そして言われたコードだけ:
public List<SavedCars> saveCars(List<Car> cars) {
return cars.stream()
.map(this::saveCar)
.collect(Collectors.toList());
}
上記のコードには、Streams関連の問題はありません。
- この
.map()
ステップが実行されるのは.collect()
(公式ドキュメントがの代わりに推奨する変更可能な縮小操作.forEach(list::add)
)が.map()
の出力に依存しており、この(つまりsaveCar()
の)出力が入力と異なるため、ストリームが「証明できない」ためです。その [eliding] 「それは計算の結果に影響を与えません。
- それは
parallelStream()
そうではないので、以前は存在しなかった同時実行性の問題を導入すべきではありません(もちろん、誰かが.parallel()
後で追加した場合、問題が発生する可能性がありfor
ます—内部計算のために新しいスレッドを起動してループを並列化することを決定した場合と同様です))。
これは、その例のコードがGood Code™であることを意味するものではありません。.stream.map(::someSideEffect()).collect()
コレクション内のすべてのアイテムに対して副作用操作を実行する方法としてのシーケンスは、よりシンプル/ショート/エレガントに見えますか?そのfor
対応物より、そしてそれは時々そうかもしれません。しかし、ユージーン、ホルガー、その他の何人かがあなたに言ったように、これに取り組むより良い方法があります。
簡単に考えると、アイテムの数が多い場合を除いて、aを起動Stream
するか、シンプルを反復するコストはfor
無視できません。アイテムが多い場合は、次のようになります。a)新しいDBアクセスを作成したくないそれぞれについて、APIの方が良いでしょう。およびb)処理のパフォーマンスを大幅に低下させたくないsaveAll(List items)
アイテムを順番に並べるので、最終的に並列化を使用することになり、まったく新しい一連の問題が発生します。