Java | Java 詳細・モダン文法:Optional – Optional.filter

Java Java
スポンサーリンク

Optional.filter を一言でいうと

Optional.filter
「中身が“あるときだけ”条件をチェックし、条件を満たさなければ空にしてしまう」
ためのメソッドです。

Stream の filter と同じ発想で、Optional 版の「条件によるふるい落とし」と思ってください。
if (x != null && 条件) というお決まりパターンを、optional.filter(条件) という形で書けるようにするための道具です。


まずは「null チェック版」との対比でイメージする

null で書くとどうなるか

「ユーザーがいるかもしれないし、いないかもしれない。
さらに、18 歳以上なら処理したい」という状況を考えます。

User user = findUserOrNull(); // 見つからなければ null

if (user != null && user.getAge() >= 18) {
    System.out.println(user.getName() + " は成人です");
}
Java

ここでは、

  1. user が null かもしれないのでチェックする
  2. null でなければ年齢を見て、18 歳以上なら処理する

という二段階の if になっています。

Optional.filter で書くとどうなるか

同じことを Optional で書き直します。

Optional<User> maybeUser = findUser(); // 見つからなければ Optional.empty()

maybeUser
        .filter(u -> u.getAge() >= 18)
        .ifPresent(u -> System.out.println(u.getName() + " は成人です"));
Java

ここで起きていることは、さっきの if とまったく同じです。

中身がなければ、filter は何もせずにそのまま空。
中身があれば、u -> u.getAge() >= 18 を評価し、true ならそのまま、false なら空にする。

「null じゃないか?」と「条件を満たすか?」を、
if (user != null && 条件) ではなく
optional.filter(条件)
という 1 ステップにまとめているイメージです。


Optional.filter の動きを丁寧に分解する

中身がある & 条件を満たす場合

Optional<Integer> maybeNum = Optional.of(10);

Optional<Integer> result =
        maybeNum.filter(n -> n > 5);

System.out.println(result); // Optional[10]
Java

流れはこうです。

  1. 中身 10 を取り出す
  2. n > 5 を評価 → true
  3. 元の値 10 をそのまま Optional.of(10) として返す

中身があるが、条件を満たさない場合

Optional<Integer> maybeNum = Optional.of(3);

Optional<Integer> result =
        maybeNum.filter(n -> n > 5);

System.out.println(result); // Optional.empty
Java

流れはこうです。

  1. 中身 3 を取り出す
  2. n > 5 を評価 → false
  3. Optional.empty() を返す(中身を捨てる)

そもそも中身がない場合

Optional<Integer> maybeNum = Optional.empty();

Optional<Integer> result =
        maybeNum.filter(n -> n > 5);

System.out.println(result); // Optional.empty
Java

中身がないので、条件は評価されません。
そのまま Optional.empty() が返ってきます。

ここで一番大事なのは、

「中身がないときに、わざわざ何かする必要がない」
「“あるときだけ”条件をチェックしてくれる」

という性質です。
これのおかげで、if (x != null && 条件) を書かなくてよくなります。


filter を挟むと「安全なパイプライン」が作れる

map と組み合わせる例

「ユーザーがいて、かつアクティブなユーザーなら、そのメールアドレスを取り出したい」というケースを考えます。

Optional<User> maybeUser = findUser();

Optional<String> maybeEmail =
        maybeUser
                .filter(User::isActive)   // アクティブなユーザーだけ通す
                .map(User::getEmail);     // メールアドレスに変換
Java

ここでの動きはこうです。

ユーザーがいない → 最初から empty のまま
ユーザーはいるが非アクティブ → filter で empty になる
アクティブユーザーがいる → map でメールアドレスに変換される

「条件を満たさないなら、その時点で“なかったこと”にする」という設計が、filter 1 行で表現できます。

そのまま orElse につなげる

さらに、「条件を満たすユーザーがいなければデフォルト値を使う」としたいなら、こう書けます。

String email =
        findUser()
                .filter(User::isActive)
                .map(User::getEmail)
                .orElse("no-reply@example.com");
Java

filter で「対象を絞る」、
map で「形を変える」、
orElse で「なかったときの扱いを決める」。

この三段構えが、Optional 設計の基本パターンの一つです。


「if で unwrap する」書き方からの卒業

やりがちなパターン

Optional を使い始めたときに一番やりがちなのが、こういう書き方です。

Optional<User> maybeUser = findUser();

if (maybeUser.isPresent() && maybeUser.get().getAge() >= 18) {
    User user = maybeUser.get();
    System.out.println(user.getName() + " は成人です");
}
Java

isPresent()get() をセットで使っていて、
せっかく Optional を導入したのに、結局「null 的な扱い」に戻ってしまっています。

filter + ifPresent に書き換える

同じことは、こう書けます。

findUser()
        .filter(u -> u.getAge() >= 18)
        .ifPresent(u -> System.out.println(u.getName() + " は成人です"));
Java

isPresent / get コンボをやめて、

「条件でふるいにかける」→ filter
「あるときだけ何かする」→ ifPresent

という役割分担にすると、コードの意図が一気に読みやすくなります。


Stream.filter との対応で理解を深める

Stream.filter とほぼ同じ感覚で使える

Stream の filter はこうでした。

List<Integer> evens =
        nums.stream()
            .filter(n -> n % 2 == 0)
            .toList();
Java

「条件を満たす要素だけを通す」という意味です。

Optional の filter も、まったく同じ発想です。

Optional<Integer> maybeEven =
        maybeNum.filter(n -> n % 2 == 0);
Java

違いは、「要素が 0 個か 1 個か」というだけ。

Stream は「0 個以上の要素の流れ」
Optional は「0 個か 1 個の要素の箱」

なので、filter の意味もそのままスライドして考えられます。


Optional.filter を設計にどう活かすか

「条件を満たさないなら“なかったこと”にする」場面で使う

filter が本領を発揮するのは、

「値はあるけれど、この条件を満たさないなら“なかったこと”にしたい」

という場面です。

例えば、

ログイン中のユーザーがいるかもしれない
いても、BAN されているユーザーなら扱いたくない

というときは、こう書けます。

Optional<User> maybeActiveUser =
        getCurrentUser()
                .filter(user -> !user.isBanned());
Java

getCurrentUser() が「ログイン中のユーザー(いなければ empty)」を返し、
filter が「BAN されていないユーザーだけ通す」。

結果として、

ログインしていない → empty
ログインしているが BAN → empty
ログインしていて BAN されていない → そのユーザーが入った Optional

という状態になります。

「条件チェックを if ではなく“宣言”として書く」

null ベースだと、どうしても if が増えます。

User user = getCurrentUserOrNull();
if (user != null && !user.isBanned()) {
    // ここでだけ user を使う
}
Java

Optional ベースだと、条件チェックも「宣言的」に書けます。

getCurrentUser()
        .filter(user -> !user.isBanned())
        .ifPresent(user -> {
            // ここでだけ user を使う
        });
Java

「ログイン中で、かつ BAN されていないユーザーが“いるなら”この処理をする」
という意図が、そのままコードの形になります。


まとめ:Optional.filter を自分の言葉で説明するなら

あなたの言葉で Optional.filter を説明するなら、こうです。

Optional.filter は、“中身があるときだけ条件をチェックし、条件を満たさなければ Optional を空にしてしまう”メソッド。
if (x != null && 条件) というパターンを、optional.filter(条件) として安全かつ読みやすく書けるようにするための道具で、maporElse と組み合わせると、“あるかもしれない値”に対する条件分岐を宣言的に表現できる。」

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