読者です 読者をやめる 読者になる 読者になる

Java8のOptionalの使い方について

Java
スポンサーリンク

Java SE 8 から新たに追加された Optional クラスについて使い方をまとめました。

Optionalとは

Java SE 8 で新たにjava.util.Optionalクラスが導入されました。Optional クラスは値を持たない場合がある(nullである)ことを表すコンテナオブジェクトで、nullをより安全に扱うためのメソッドを提供しています。

今までは戻り値が null の可能性がある場合は NullPointerException を防ぐために if文で null チェックをしていました。

Person person = getPerson();
if (person != null) {
    person.getName();
}

Optionalを使用すると値が存在する(nullでない)場合だけ処理することを一文で表現できます。

Optional<Person> personOpt = Optional.ofNullable(getPerson());
personOpt.ifPresent(person -> person.getName());

 Optional (Java Platform SE 8)

Optionalの生成

Optional のコンストラクタはprivateコンストラクタのみであるため、インスタンスを生成する際は static メソッドを使用します。

staticメソッド 戻り値
Optional.empty() 値が null の Optionalインスタンス
Optional.of(T value) 値が value の Optionalインスタンス
Optional.ofNullable(T value) 値が null の場合はempty、nullでない場合はofメソッドの結果

Optional を使用するパターンというのは、値を持たない場合がある事を考慮する必要がある時になると思います。そのためOptional.ofNullableで全て賄う事も可能ではありますが、値が入る事が確実な場合にはOptional.ofを使用し、null を返したい処理の場合はOptional.emptyを使用した方が明示的で可読性も上がります。Optional.ofで null を与えると NullPointerException が発生しますので、Optional.ofNullableのみだと本当の異常データが見逃されてしまいますよね。

Optionalから値を取得する

ここからはインスタンスメソッドになります。Optionalから値を取得するメソッドは以下の4つです。基本的には保持している値を返すメソッドになりますが、値が無い場合の処理としてパターンが分けられています。

メソッド 値が無い場合の処理
get() NoSuchElementException をスロー
orElse(T other) 引数で指定した値を返す
orElseGet(Supplier<? extends T> other) Supplier の get メソッドを実行
orElseThrow(Supplier<? extends X> exceptionSupplier) Supplier の get メソッドを実行

getメソッドは値が存在するのが確実な時に使用します。値が null の時は NoSuchElementException をスローします。そのため、Optional を使用する場合にはあまり出番の少ないメソッドです。

orElseメソッドは値が null の場合に、引数で指定したインスタンスを返してくれます。値を持ってない時のデフォルト値を指定できるということですね。また、当然 String の Optional に Integer のデフォルト値を設定するという事などはできません。

orElseGetメソッドは基本的にはorElseメソッドの場合と同じですが、関数型インターフェースの Supplier を引数にとります。Supplier は引数なしで値を返す処理でしたね。使い所としては Supplier の getメソッドが呼ばれた時だけ渡したラムダ式が呼ばれるので、重たい処理を行う場合に有効です。遅延評価といいます。

orElseThrowメソッドもofElseGetと同様に Supplier を引数にとりますが、こちらは値が null の場合に投げる例外を指定します。

Optionalの値を判定する

isPresentメソッドは値が存在すれば true、値が存在しない場合は false を返すだけのメソッドです。従来型のif (result != null)という判定と変わりませんね。getメソッドと同じようにあまり使う機会がなく、使ったら負けとまで言われてるようですが、私はそこそこ使う事がありそうな気もします。

Optionalの値を処理する

ifPresentメソッドは関数型インターフェースの Consumer を引数にとり、値が null でなければConsumer#acceptを実行します。Consumer は値を返さずに副作用を起こすための処理です。

isPresentgetメソッドを使用すると従来型の null チェックと変わらない書き方になってしまいます。

if (personOpt.isPresent()) {
    personOpt.get().getName();
}

このような場合はifPresentを使用した方がいいでしょう。

personOpt.ifPresent(person -> person.getName());

Optionalを処理してOptionalで返すメソッド

ここであげるメソッドは Stream でも同様のメソッドがありますので、既にご存知の場合は馴染みやすいかと思います。戻り値が Optional のメソッドはmap flatMap filterの3種類です。

filter

fileterメソッドは値が存在し、それが指定された条件に一致する場合はその値を記述するOptionalを返し、そうでない場合は空のOptionalを返します。判定を行うための関数型インターフェースである Predicate を引数にとります。基本的には Stream#filter と同じですが、根本的に違う部分もあるので気をつけましょう。処理は以下の通りです。

  • Optionalに保持する値が null の場合 -> Optional.empty()を返す。
  • Predicate#testメソッドの結果が false の場合 -> Optional.empty()を返す。
  • Predicate#testメソッドの結果が true の場合 -> そのままOptionalを返す。

Stream の filter と違う部分とは結果が false の時も空の Optional を返すという点です。Stream#filterの場合は条件に一致するものだけで構成される Stream を返すので気を付けましょう。

map

mapメソッドは値が存在する場合は、指定された関数型インターフェースの Function をその値に適用し、結果が null でなければ結果を保持した Optional を返します。Function は値を変換するための関数型インターフェースです。基本的には Stream の map と同じような感じですが、内部では以下のような処理をしています。

  • Optionalに保持する値が null の場合 -> Optional.empty()を返す。
  • Function#applyメソッドの結果が null の場合 -> Optional.empty()を返す。
  • Function#applyメソッドの結果が null 以外の場合 -> Optionalに値を保持して返す。

flatMap

flatMapメソッドは値が存在する場合は、指定されたOptional生成マッピング関数をその値に適用し、その結果を返します。そうでない場合は空のOptionalを返します。Optional を返す Function を引数にとりますが Optional がネストされていくことはありません。同じ事を map でやろうとするとネストしてしまいます。

  • Optionalに保持する値が null の場合 -> Optional.empty()を返す。
  • Function#applyメソッドの結果が null の場合 -> NullPointerExceptionをスローする。
  • Function#applyメソッドの結果が null 以外の場合 -> 結果の Optional をそのまま帰す。

関連記事

Streamのfilter map flatMapについても合わせて紹介します。

 Java8のStream APIの使い方(中間操作編① - filter, map) - TASK NOTES

 Java8のStream APIの使い方(中間操作編② - flatMap, distinct, limit, skip) - TASK NOTES

Java技術書