Java Tips | 基本ユーティリティ:Optionalラッパー

Java Java
スポンサーリンク

Optional ラッパーってそもそも何者か

Java の Optional<T> は、「値があるかもしれないし、ないかもしれない」を安全に表現するためのコンテナ型です。null をそのまま返したり受け取ったりすると NullPointerException の温床になりますが、Optional を使うと「ここは値がない可能性がある」ということを型レベルで表現できます。

業務コードでは、検索結果や設定値など「見つからないこともある」「設定されていないこともある」といった場面で特に威力を発揮します。Optional 自体は Java 標準ライブラリの一部ですが、うまく使うために「Optional ラッパー的なユーティリティ」を用意しておくと、コードがかなり読みやすくなります。


Optional の基本をきちんと押さえる

Optional の作り方と「ラップする」という感覚

Optional は「値を包む箱」とイメージすると分かりやすいです。箱の中に値が入っているかもしれないし、空かもしれない。その箱を扱うのが Optional です。

import java.util.Optional;

Optional<String> name1 = Optional.of("Taro");        // 絶対に null ではない値
Optional<String> name2 = Optional.ofNullable(null);  // null かもしれない値
Optional<String> name3 = Optional.empty();           // 空の Optional
Java

Optional.of(value) は「絶対に null ではない」ときだけ使います。null を渡すと即座に例外になります。Optional.ofNullable(value) は「null かもしれない」値を安全に包むためのメソッドで、業務コードではこちらを使う場面が圧倒的に多いです。

ここで大事なのは、「null をそのまま返さず、いったん Optional に包む」という発想です。これが「Optional ラッパー」の一番基本的な使い方です。

値の取り出し方とやってはいけない get 直叩き

Optional から値を取り出すメソッドはいくつかありますが、初心者が一番やりがちで、かつ避けるべきなのが get() の直叩きです。

Optional<String> nameOpt = Optional.ofNullable(getUserName());

// これは NG パターン
String name = nameOpt.get();  // 中身が空だと NoSuchElementException
Java

get() は「絶対に中身がある」と分かっているときだけ使うべきで、業務コードではほとんど出番がありません。代わりに、次のようなメソッドを使います。

String name = nameOpt.orElse("名称未設定");  // なければデフォルト値
Java

orElse は「中身があればその値、なければ指定したデフォルト値」を返します。これが一番素直で分かりやすい取り出し方です。


実務でよく使う Optional ユーティリティ的メソッド

orElse / orElseGet / orElseThrow の違いを深掘り

orElse 系メソッドは、業務コードで頻出です。それぞれの違いをきちんと理解しておくと、「なんとなく使う」から一段レベルアップできます。

Optional<String> nameOpt = Optional.ofNullable(getUserName());
Java

orElse は「常に右側の式を評価する」点がポイントです。

String name = nameOpt.orElse(createDefaultName());  // createDefaultName() は必ず実行される
Java

一方、orElseGet は「中身が空のときだけ」右側のラムダが実行されます。

String name = nameOpt.orElseGet(() -> createDefaultName());  // 空のときだけ実行
Java

デフォルト値を作る処理が重い場合や、副作用を持つ場合は orElseGet を使うほうが安全で効率的です。

orElseThrow は「値がなければ例外を投げる」ためのメソッドです。

String name = nameOpt.orElseThrow(
    () -> new IllegalStateException("ユーザー名が必須なのに見つかりません")
);
Java

「ここは本来必ず値があるべきだが、なかったらバグなので落としてよい」という場面で使います。Objects.requireNonNull と同じく、「ここは必須」という設計をコードで表現するための強い道具です。

ifPresent / ifPresentOrElse で「あるときだけ処理する」

Optional の良さは、「null チェック」と「処理」を一体化して書けるところにもあります。

Optional<String> nameOpt = Optional.ofNullable(getUserName());

nameOpt.ifPresent(name -> {
    System.out.println("こんにちは、" + name + "さん");
});
Java

ifPresent は「中身があるときだけラムダを実行する」メソッドです。if (name != null) { ... } をより宣言的に書いたものだと考えると分かりやすいです。

Java 9 以降では ifPresentOrElse も使えます。

nameOpt.ifPresentOrElse(
    name -> System.out.println("こんにちは、" + name + "さん"),
    ()   -> System.out.println("ゲストさん、ようこそ")
);
Java

「あるときの処理」と「ないときの処理」を 1 か所にまとめて書けるので、条件分岐が散らばらず、業務ロジックが読みやすくなります。


map / flatMap / filter で「Optional ラッパーらしさ」を活かす

map で中身だけを変換する

map は「箱の中身があれば変換し、なければ何もしない」というメソッドです。ここが Optional らしさの核心です。

Optional<String> nameOpt = Optional.ofNullable(getUserName());

Optional<Integer> lengthOpt = nameOpt.map(String::length);
Java

nameOpt が空なら lengthOpt も空、値があればその長さが入ります。null チェックを書かなくても、「あるときだけ変換する」という処理を自然に表現できます。

業務コードだと、例えばユーザーからメールアドレスを取り出すときにこう書けます。

Optional<User> userOpt = findUserById("u001");

Optional<String> emailOpt = userOpt.map(User::getEmail);
Java

userOpt が空なら emailOpt も空なので、「ユーザーがいないのにメールアドレスだけある」という矛盾した状態が起きません。

flatMap で Optional の入れ子を解消する

map を使っていると、たまに Optional<Optional<T>> のような入れ子になってしまうことがあります。そこで使うのが flatMap です。

Optional<User> userOpt = findUserById("u001");

// User#getEmailOpt() が Optional<String> を返すとする
Optional<String> emailOpt = userOpt.flatMap(User::getEmailOpt);
Java

flatMap は「中身を取り出して、そのままフラットにする」イメージです。map だと Optional<Optional<String>> になってしまうところを、flatMap なら Optional<String> のまま保てます。

filter で条件に合うときだけ残す

filter は「条件に合うときだけ中身を残し、合わなければ空にする」メソッドです。

Optional<String> nameOpt = Optional.ofNullable(getUserName());

Optional<String> longNameOpt =
        nameOpt.filter(name -> name.length() >= 5);
Java

名前が 5 文字以上ならそのまま、そうでなければ空の Optional になります。業務ロジックの条件を、if 文ではなく「フィルタ」として書けるのが気持ちいいところです。


「Optional ラッパー」な自作ユーティリティの例

よくあるパターンを 1 か所にまとめる

現場では、「毎回同じような Optional 操作を書いているな」と感じたら、自前のユーティリティでラップしてしまうことがあります。これがまさに「Optional ラッパー」です。

import java.util.Optional;
import java.util.function.Supplier;

public final class Optionals {

    private Optionals() {}

    // null かもしれない値を安全に Optional に包む
    public static <T> Optional<T> wrap(T value) {
        return Optional.ofNullable(value);
    }

    // null または空文字なら空 Optional にする
    public static Optional<String> nonBlank(String value) {
        return Optional.ofNullable(value)
                       .filter(s -> !s.isBlank());
    }

    // Optional が空ならデフォルト値を返す(Supplier 版)
    public static <T> T orElseGet(Optional<T> opt, Supplier<T> defaultSupplier) {
        return opt.orElseGet(defaultSupplier);
    }
}
Java

これを使うと、業務コード側はかなりスッキリします。

String rawName = getUserNameFromLegacyApi();  // null や空文字が返ることがある

String name = Optionals.nonBlank(rawName)
                       .orElse("名称未設定");
Java

「null や空文字をまとめて扱う」「デフォルト値を遅延評価する」といったパターンを 1 か所に集約できるので、バグの入り込みやすい境界処理が安定します。


Optional のベストプラクティスと「やってはいけない使い方」

どこで使うべきか、どこで使うべきでないか

Optional は万能ではなく、「使う場所を間違えると逆に読みづらくなる」という性質があります。ここは実務でかなり重要なポイントです。

一般的な指針としては、次のようになります。

戻り値には積極的に使う。
検索メソッドや設定取得メソッドなど、「値がないこともある」ことを表現したいときに最適です。

public Optional<User> findUserById(String id) {
    User user = ...;  // 見つからなければ null
    return Optional.ofNullable(user);
}
Java

引数やフィールドには基本的に使わない。
Optional を引数にすると、呼び出し側が「Optional を渡すのか null を渡すのか」迷いやすくなり、かえって設計が複雑になります。フィールドに使うと、永続化やシリアライズのときに扱いづらくなります。

また、「Optional を返すメソッドが null を返す」のは完全にアンチパターンです。Optional 自体が「null を返さないための仕組み」なので、そこに null を混ぜると意味が崩壊します。

get と isPresent の組み合わせに頼りすぎない

isPresent()get() を組み合わせて、結局 if 文を書くスタイルも、あまり推奨されません。

// あまり良くない書き方
if (nameOpt.isPresent()) {
    String name = nameOpt.get();
    System.out.println(name);
}
Java

これだと、「Optional を使っているのに、結局 null チェックと同じことをしている」状態です。代わりに ifPresentorElsemap などを使うと、より宣言的で読みやすいコードになります。


まとめ:初心者が Optional ラッパーを身につけるステップ

ステップ 1:Optional を「null を包む箱」として理解する

Optional.ofNullable(value) で「null かもしれない値を包む」、orElse で「なければデフォルト値を返す」という基本パターンを、まずは手に馴染ませてください。

ステップ 2:orElse 系と ifPresent 系を使い分ける

「値がないときにどうするか」を、orElse / orElseGet / orElseThrow で明示的に書けるようになると、業務ロジックの意図がぐっと伝わりやすくなります。ifPresent / ifPresentOrElse で「あるときだけ処理する」スタイルにも慣れておくと良いです。

ステップ 3:map / flatMap / filter で「ラッパーらしさ」を活かす

map で変換し、flatMap で入れ子を解消し、filter で条件をかける。この 3 つを使いこなせるようになると、「Optional をただの null チェック代わりに使う」段階から、「設計の一部として使う」段階に進めます。

ここまで来れば、あなたはもう「Optional に振り回される側」ではなく「Optional を使いこなす側」です。

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