Java | Java 詳細・モダン文法:Stream API 深掘り – findFirst / findAny

Java Java
スポンサーリンク

findFirst / findAny を一言でいうと

findFirstfindAny は、
「Stream の中から“1 件だけ”要素を取り出すための終端操作」です。

どちらも戻り値は Optional<T> で、
「見つかればその要素を包んだ Optional」「見つからなければ空の Optional」を返します。

ざっくり言うと、

  • 順番が大事で「一番最初のもの」が欲しいときは findFirst
  • 順番はどうでもよくて「どれか 1 件あればいい」ときは findAny

という使い分けになります。


共通点:どちらも Optional で「1 件だけ」返す

シグネチャと基本イメージ

findFirst のシグネチャは次の通りです。

Optional<T> findFirst()
Java

findAny のシグネチャは次の通りです。

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
    }
}
Java

filter で「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)
    }
}
Java

filter の結果は「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 を使うと、findFirstfindAny の違いがはっきり出てきます。

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");
Java

orElseThrow で「見つからなければ例外」。

String name =
        names.stream()
             .filter(n -> n.startsWith("Z"))
             .findFirst()
             .orElseThrow(() -> new IllegalArgumentException("not found"));
Java

ifPresent で「見つかったときだけ処理する」。

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

タイトルとURLをコピーしました