Menu

Java Optional: null と NullPointerException を避ける

java.util.Optional とは何か、どう作るか、そして null チェックの代わりに map・filter・orElse・ifPresent を使って値を安全に読み取る方法。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

Optional が解決する問題

答えを持たないかもしれないメソッドは、Java では昔から厄介な選択を迫られてきました。null を返して、呼び出し側がチェックを覚えていてくれることを願う、というものです。たいていは覚えておらず、その結果、null を返したメソッドから遠く離れた場所で NullPointerException が現れます。

Optional<T> は、値を含むか、あるいは明示的に空であるかのどちらかである小さな箱です。User の代わりに Optional<User> を返すことで、メソッドはそのシグネチャの中で呼び出し側に「これは何も見つけないかもしれない」と伝え、コンパイラはそのケースを処理するよう導きます。

Optional の作成

ファクトリメソッドは 3 つあり、適切なものを選ぶことが重要です。

  • Optional.of(value) - 値は非 null でなければならず、そうでなければその場で NullPointerException を投げます。
  • Optional.ofNullable(value) - 値が null なら空、そうでなければ値を持ちます。null になりうるものを包むときに使います。
  • Optional.empty() - 空の optional です。

よくある間違いは、null になりうる値に対して Optional.of(x) に手を伸ばすことです。それは目的を台無しにし、まさに避けようとしていた例外を投げます。迷ったら ofNullable を使いましょう。

値を安全に読み取る

optional を手にしたら、目標はそこに値があると仮定せずに値を取り出すことです。乱暴な手段が get() で、ほとんど使うべきではありません。

Optional<String> name = Optional.empty();
String value = name.get();   // NoSuchElementException を投げる - 変装した NPE

代わりに、フォールバックを用意するか、存在に反応しましょう。orElse は空のときデフォルト値を返し、ifPresent は値が存在するときだけコードを実行します。

デフォルト値の生成にコストがかかる場合は、orElse の代わりに orElseGet(supplier) を使いましょう。supplier は optional が実際に空のときだけ実行されます。orElse の引数は、必要のないときでも常に評価されます。

「これは存在するはず」のための orElseThrow

空であることが本当にエラーである場合もあります。必須の設定値や、データベースにいるはずのユーザーなどです。orElseThrow は、空の optional を、あなたが選んだ明確な例外に変えます。

これは if (opt.isPresent()) ブロックよりずっと読みやすく、失敗が起きるその地点で失敗を明示します。

map と filter で変換する

本当の利点はチェーンにあります。map値が存在するときだけ関数を値に適用し、空の optional は空のままにします。つまり null に一度も触れることなく変換できます。filter はテストに通らなければ値を捨てます。

マッピング関数自体が Optional を返す場合は、厄介な Optional<Optional<T>> を避けるために map の代わりに flatMap を使いましょう。これはストリームで見た map/flatMap の区別を反映しています。Optional は、0 個か 1 個の要素を持つストリームによく似た振る舞いをします。

Optional が向く場所(と向かない場所)

Optional は、正当に結果を生成しないことがあるメソッドの戻り値の型として設計されています。代表例は Stream.findFirst()Map スタイルの検索、そしてパースです。そこで使えば、API は自身の欠落を自ら文書化します。

次の用途には向きません

  • フィールド。 シリアライズ不可能で、フィールドごとにオブジェクトを 1 つ増やします。素の参照を使い、コンストラクタで検証しましょう。
  • メソッドパラメータ。 すると呼び出し側は引数を Optional.of(...) で包む必要があり、オーバーロードや null 許容パラメータよりも煩雑になります。
  • コレクション。 Optional<List> ではなく、空の List を返しましょう。空のコレクションはすでに「何もない」を意味します。

戻り値の型として扱えば、Optional は静かな null バグを、欠落したケースを処理するためのコンパイル時の促しへと変えます。

次: 例外

Optional は「値が存在しないかもしれない」という日常的なケースをきれいに扱いますが、一部の失敗は本当に例外的です。開けないファイル、切れるネットワーク、パースできない入力などです。Java はそれらを例外でモデル化します。それが次のページです。

よくある質問

Java の Optional とは何で、なぜ使うのですか?

Optional<T> は、値を保持しているか、あるいは空であるかのどちらかのコンテナです。メソッドの戻り値の型において「値が存在しないかもしれない」ことを明示するために存在し、呼び出し側が null チェックを忘れて実行時に NullPointerException に遭遇するのではなく、空のケースを扱うよう促します。主に、何も見つからない検索のように結果を生成しないかもしれないメソッドの戻り値の型として使いましょう。

Optional.of と Optional.ofNullable の違いは何ですか?

Optional.of(x)x が null だとその場で NullPointerException を投げます。値が null でないと分かっているときに使います。Optional.ofNullable(x)x が null のとき空の Optional を、そうでないとき値を持つものを返します。値が null になりうるときに使います。Optional.empty() は空の optional を直接得られます。

値を読み取るために Optional.get() を呼ぶべきですか?

素の get() は避けましょう。optional が空のとき NoSuchElementException を投げますが、これは名前を変えただけの NullPointerException にすぎません。空のケースが常に処理されるよう、orElseorElseGetorElseThrowifPresentmap を優先してください。どうしても先に確認する必要があるなら isPresent() でガードしますが、関数型のメソッドの方がすっきりします。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める