日々常々

ふつうのプログラマがあたりまえにしたいこと。

「いちばんめ」が大事でないならfindFirstよりfindAnyを使う

Javaの Stream の話です。

Stream は複数のものを扱いますが、結果が1つ(もしくは0 or 1個)だけ欲しい場合がよくあります。 こういう時に使用できるのが findFirstfindAny です。

jshell> Stream.of("hoge", "fuga", "piyo").findFirst()
$1 ==> Optional[hoge]

jshell> Stream.of("hoge", "fuga", "piyo").findAny()
$2 ==> Optional[hoge]

結果は同じで、先頭の "hoge" が返っています。findなんで Optional で包まれています。

実際の使用シーンはコレクションをIDとかで filter して、1件に絞られている場合とか、filter の条件を満たすものならどれでもいい場合です。 filterせずに findFirst() するのはあまりない気がする。そんなものは最初から Optional にしとけって感じだし。

こういう時に findFirst() が使われがちです。AIも findFirst() を生成してきます。ですがーー

「いちばんめ」に強い意志がないならば findAny() を選ぶ のがいいです。

実装している時は軽い気持ちで選ぶでしょうが、何か不具合があった時にこういうコードを見ると「これfirstでないとダメなのかな?」とか「このStreamの順序ってどうなってるんだっけ?」とか考えなきゃいけなくなります。まじめんどい。

脱線:2件以上あったらまずいときの話

「1件しか入っていないから findFirst() でいいや」としている場合。 2件以上入ってたら変なことになるけど目を瞑って findFirst() している場合。前者も大概だけど、後者のほうが罪深いです。

実は複数件入っているとかに起因する不具合がそれなりに見ます。わかりづらいです。 取得できて処理が続行できるので、不正な状態になっていることが握りつぶされている。2件目以降は消え去るので、本番でしか起こらないとかだとほんと調査しづらい。

もし「1件であること」が大事なのなら findFirst() を使ってはいけないです。もちろん findAny() もだめです。 ただ findAny() を選択している場合は「複数あったらどれが返ってもいいやつなんだよな」という確認が入っている感じがします。 FirstとAnyの違いに意識を向けられる場合は複数件にも意識が向く感じ。完璧じゃないけど結構な精度があるとは思う。 思考停止や「 findFirst() はグチグチ言われるから findAny() にする」とかだと効果ないですけどね。

やるなら一度 toList() とかでコレクションにして、サイズチェックしてから1件取り出しです。 ものによっては迂闊に toList() したら思った以上の件数があってOOMEとかも起こったりするので、 .limit(2).toList() とかですかね。

めんどくさいのはわかるけど、障害のほうがめんどくさいですし、お金もかかります。せっかくJavaつかってんだから実装で防げるものは防ぐのがいいです。

reduce で2件目以降がきたら例外にするとかもできます。ちゃっぴーに書いてもらったらこんな感じ。

T value = stream.reduce((a, b) -> {
    throw new IllegalStateException("More than one element");
}).orElseThrow(() -> new IllegalStateException("No element"));

これをいろんなとこにコピペするのはNGです。やるなら共通のユーティリティとかにしましょう。

……脱線のほうが長くね?

おまけ:StreamのAPI仕様

最後に一応、Javadocも見ておきましょう。サイドにメソッド一覧とか出るようになって使いやすくなりましたね。って今日気づきました。普段IDEでみるからね。。。

安定した結果が必要な場合は、かわりにfindFirst()を使用してください。

こんな書き方をされると「安定してる findFirst() の方を使った方が良さそう」と思っても仕方ないと思います。

でももし安定した結果が大事なのであれば、ストリームが順序を持つものであることを保証しなければなりません。 Stream自身に isOrdered() みたいな判定メソッドはないため、メソッド内でListから生成されたStreamであることが明らかであり、今後もそれが維持されることが前提になります。これは思っている以上に難しいことです。 そういうことにとらわれずにやるとすれば sorted() を読んで並び替えてから findFirst() でしょうか。 sorted() で実行時例外が出ないといいですねー。

おまけ:Codexさん

あなたがいま書いてきたコードですけど。

人間なら「わかってんのになんでやらんの」と言いたくなっちゃいますが、AIにそういう説教するのは「お金払って話を聞いてもらって相槌を打ってもらう」というアレですから、アレ。