findFirst / findAny を一言でいうと
findFirst と findAny は、
「Stream の中から“1 件だけ”要素を取り出すための終端操作」です。
どちらも戻り値は Optional<T> で、
「見つかればその要素を包んだ Optional」「見つからなければ空の Optional」を返します。
ざっくり言うと、
- 順番が大事で「一番最初のもの」が欲しいときは
findFirst - 順番はどうでもよくて「どれか 1 件あればいい」ときは
findAny
という使い分けになります。
共通点:どちらも Optional で「1 件だけ」返す
シグネチャと基本イメージ
findFirst のシグネチャは次の通りです。
Optional<T> findFirst()
JavafindAny のシグネチャは次の通りです。
Optional<T> findAny()
Javaどちらも「Stream を消費して、Optional<T> を返す終端操作」です。
一度呼ぶと、その Stream は使い切られるので、同じ Stream に対して二度目は呼べません。
典型的な使い方は、「条件で絞り込んでから 1 件だけ欲しい」というパターンです。
import java.util.List;
import java.util.Optional;
public class FindBasic {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie");
Optional<String> maybeBob =
names.stream()
.filter(name -> name.startsWith("B"))
.findFirst();
maybeBob.ifPresent(System.out::println); // Bob
}
}
Javafilter で「B で始まる名前」に絞り込み、その中から 1 件だけ取り出しています。
「見つからないかもしれない」ので、戻り値は String ではなく Optional<String> になっている、というのが重要なポイントです。
findFirst:順序付き Stream の「最初の 1 件」を取る
「最初」とは何か
findFirst は、その名の通り「最初の要素」を返します。
ここでいう「最初」とは、「Stream の順序における先頭」です。
List.stream() のような順序付き Stream では、
「元のリストの順番」がそのまま Stream の順序になります。
import java.util.List;
public class FindFirstExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie", "Bob");
String firstBob =
names.stream()
.filter(name -> name.equals("Bob"))
.findFirst()
.orElse("not found");
System.out.println(firstBob); // Bob(最初に出てきた Bob)
}
}
Javafilter の結果は「Bob, Bob」で、そのうち先頭の「Bob」が返されます。
順序が意味を持つとき――
「登録順で最初のもの」「ソート済みリストの先頭のもの」などが欲しいときは、素直に findFirst を選びます。
findAny:順序にこだわらず「どれか 1 件」を取る
「どれでもいいから 1 件」の終端操作
findAny は、「順序に意味がない検索」で使うメソッドです。
import java.util.List;
public class FindAnyExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie", "Bob");
String anyBob =
names.stream()
.filter(name -> name.equals("Bob"))
.findAny()
.orElse("not found");
System.out.println(anyBob); // たいてい Bob
}
}
Java逐次実行の順序付き Stream では、実際には findFirst と同じ結果になることが多いですが、
仕様上は「どれが返るかは保証しない」とされています。
ここで大事なのは、「返ってくる要素の“位置”に意味を持たせない」ことです。
「とにかく 1 件あればいい」「どれでもいい」という前提で使うのが findAny です。
並列 Stream での違いをイメージする
findFirst は「順序を守る」、findAny は「最初に見つかったやつでいい」
parallelStream() のように並列 Stream を使うと、findFirst と findAny の違いがはっきり出てきます。
findFirst は「順序を守る」必要があるので、
並列に処理していても「本当に先頭の要素」が見つかるまで待つ必要があります。
一方 findAny は、「どのスレッドが最初に見つけた要素でもいい」ので、
より早く結果を返せる可能性があります。
初心者向けにざっくり言うと、
- 並列 Stream で「順序に意味がない検索」をするときは
findAnyの方が有利になり得る - まずは「順序が大事なら findFirst、どうでもいいなら findAny」と覚えておけば十分
という感覚で大丈夫です。
Optional と組み合わせた安全な扱い方
「見つからないかもしれない」を型で表現する
findFirst / findAny はどちらも Optional<T> を返すので、
「見つからない場合」を必ず考慮する必要があります。
よく使うパターンをいくつか見てみます。
orElse でデフォルト値を返す。
Optional<String> maybeName =
names.stream()
.filter(n -> n.length() > 10)
.findFirst();
String name = maybeName.orElse("default");
JavaorElseThrow で「見つからなければ例外」。
String name =
names.stream()
.filter(n -> n.startsWith("Z"))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("not found"));
JavaifPresent で「見つかったときだけ処理する」。
names.stream()
.filter(n -> n.startsWith("A"))
.findAny()
.ifPresent(n -> System.out.println("found: " + n));
Javaここでのポイントは、「null を返さない」「null チェックを書かない」ことです。Optional を返すことで、「見つからない可能性」を型レベルで表現できます。
設計の指針:findFirst / findAny の使い分け
自分の中でのルールを決めておく
実務レベルで迷わないために、シンプルなルールを持っておくと楽です。
順序に意味があるなら findFirst。
例えば、「登録順で最初のもの」「ソート済みリストの先頭のもの」など。
順序に意味がなく、「どれでもいいから 1 件あればよい」なら findAny。
特に並列 Stream を使うときは、こちらの方がパフォーマンス的に有利になり得ます。
どちらを使う場合でも、
「見つからない可能性がある」ことを前提に、Optional をちゃんと扱う
というのが一番大事な設計ポイントです。
まとめ:for 文からの書き換えイメージ
最後に、よくある for 文のパターンを、findFirst に書き換えてみます。
従来の書き方はこうです。
public User findById(List<User> users, int id) {
for (User u : users) {
if (u.getId() == id) {
return u;
}
}
return null;
}
Javaこれを Stream で書くと、こうなります。
import java.util.Optional;
public Optional<User> findById(List<User> users, int id) {
return users.stream()
.filter(u -> u.getId() == id)
.findFirst();
}
Java呼び出し側は、Optional<User> をちゃんと扱います。
Optional<User> maybeUser = findById(users, 10);
maybeUser.ifPresent(u -> System.out.println(u.getName()));
Java