Optionalは戻り値で使う

Java8から登場したOptionalはどこで使うモノなのかを考える。

執筆時バージョン
Java

Java SE 8

戻り値がnullの可能性があるときに使うと有効

例えば、あるオブジェクトを検索するメソッドがあるとする。

interface PersonDAO{
    Person find(PersonId id);
}

このとき値が見つからないときの挙動としては次が考えられる。

  • nullを返す。

  • Nullオブジェクトを返す。

  • 例外を投げる。

どれで提供すべきだろうか。このメソッドが1ヶ所でしか使われなければそこに合わせる形でよいが複数で呼ばれる場合はどうだろう。つきつめて考えてもどれがいいのかは利用者次第なので悩ましい。提供者側の判断を強引に押しつけてもうれしくない人が出てくる。

そんなときにOptionalが使える。

interface PersonDAO{
    Optional<Person> find(PersonId id);
}

Optionalは値を保持するラッパーで、値を取り出すメソッドをいくつか提供する。

メソッドの実装者は値が見つかった場合はOptionalで値をラップして、見つからない場合はOptional.empty()にして返す。なので値がnullの場合の挙動を、いちいち悩まなくて済む。

一方Optionalで値を受け取るクライアントは、メソッド経由で値を取り出す。このとき中身の値がnullのときにどうしたいのかによって、取り出すメソッドを選択する。

例、中身の値がnullだったときには例外としたい場合
Optional<Person> optionalPerson = personDAO.find(id);
Person person = optionalPerson.orElseThrow(IllegalStateException::new);
例、代替値にしたい場合
Optional<Person> optionalPerson = personDAO.find(id);
Person defaultPerson = … ;
Person person = optionalPerson.orElse(defaultPerson);

orElseThrow()メソッドとorElse()メソッドはともに値が存在する場合はその値を返す。存在しない場合はそれぞれ、例外を投げたり代替値を返す。

またこれらの処理にはif文が必要になるが、Optionalではそれぞれのメソッドで処理してくれるのでif文が隠れることになりコードがスッキリする。

このように戻り値にnullの可能性がある場合は、メソッド提供者も自身で選択の責任を背負い込む必要は無いし、クライアント側も押しつけられることなく自身で選択できるので、両方ともうれしい。よって戻り値の場合の利用は有効ということがわかる。

戻り値以外での利用はやめておく

Optionalに慣れてくると便利に思えるのであらゆるところで使えないかと考えるが、実は戻り値以外での利用できるように設計されていない。というか使われないようにしてある。

そのいちばんの象徴がSerializableではないこと。これによってフィールドで実質利用できず、オブジェクトの保存で困る。(私は知らずに面倒なことになりました…)

なぜそうなっているのかというと、意図的にそうやっているということ。 これはきしださんのブログに書かれている。

Optionalは意図的にSerializableではなくなってますね。

でその意図としては、一旦Serializableにして出力形式を決めてしまうと、今後ずっとその出力形式を維持しないといけないことになるので、そのメンテナンスコストを嫌ったというのがあるようです。

フィールド以外もよく考えると、利用しにくい。

利用箇所 有効性 補足

フィールド

×

Serializableではないので保存時に困る。

メソッドの引数

×

Optionalの生成に一手間かかる。オーバーロードで十分。

メソッドの戻り値

有効な場面が多い。

ローカル変数

×

Optionalの生成に一手間かかるのでやり過ぎ。

このように戻り値以外は、ほぼ利用できない。

Java標準ライブラリでの使われ方を確認するとStreamクラスのfindAny()やmax()などがある。これらはStreamが空だった場合が考えられるのでOptionalを返すとしっくりくる。やはり、こういう使い方がベター。

よってOptionalを使う場所としては、戻り値は十分使えるモノとして頭に入れておいて、それ以外での利用はそうとう慎重にする。

Appendix B: 改訂履歴

  • v1.0, 2014-11-02: 初稿