エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

【極論ですけど】JavaでforEachを使ったら負けだと思う

正直使い所がわからないんですよね、forEach.
ログとかprintfとかする以外は…

コレクションを更新するな

未だに時々見るのがこういうコード。

List<X> list = new ArrayList<>();
適当なコレクション.forEach()
  .map(t -> list.add(t.getXXXX()));

これとっても良くない。

スレッドセーフでない

上記をあまり深く考えずにパラレルストリームにしちゃいますと

List<X> list = new ArrayList<>();
適当なコレクション.forEach().parallelStream()
  .map(x -> list.add(x.getXXXX()));

ArrayListに対する追加処理がスレッドセーフでないため、確率的に不具合が起きてしまいます。*1

パラレルにしなければいい…のですが、Project Lambdaの目的の一つがマルチコアへの対応なわけで、「parallelStreamを使って並列化できないものはラムダ式で書くな」くらいに私は思っています。(私の勝手ルールではありますが)

Lambdaで更新するの「関数」でない

Lambda式内では外で定義した変数に代入ができません。
これは、「状態」を極力排除することによって、バグの入り込む余地を減らすため… と私は認識しています。
ですが、上記のようにコレクションに対する操作や、Setterでの代入はJavaの言語仕様上できてしまいます。

…とはいえ、できるからやっていい、というものでもないと思います。

「リストを作る」と明確に書く方法がある

List<X> list = 適当なコレクション.stream() //parallelも可
  .map(X::getXXXX)
  .collect(Collector.toList());

そもそもこのように、リストを作るための書き方が用意されているのに、これを使わずに汎用的なforEachでなんとかする… というのは美しさに欠けます。

更新しないとなると、forEachは出力くらいしか使いみちがないのでは

上で書いたとおり、コンパイルエラーにはならない… とはいえ、Lambda式内で何かを「更新」するのは不適切と考えます。
そうなると、冒頭に書いたとおり、何かの出力くらいしか使いみちがないなぁ… ということになり、
よっぽどのことがない限り、「forEachを使ったら負け」だと思うのです。

*1:私が経験したことのある事象だと、「適当なコレクション」よりも多い要素がlistに追加されるという謎現象が発生し、その要素をgetするとnullが入ってる → ヌルポで落ちる というものを何度かみました。