Optional型を使うといわゆるヌルポから解放されるといいますが、どのような場合に使うとよいでしょうか?
参照先の値がnullになる可能性がある、あるいは、nullにならないことがわかっていないケースで、参照先の値をOptional経由で取り出すと、参照先がnullでない場合にだけ値を取り出せるので、Optionalのありがたみがわかります。
たとえば、変数userにユーザー情報が入っていて、user.getCompany()でユーザーの会社情報が取得でき、user.getCompany().getCompanyName()で会社名が取得できるとします。
このとき、user,user.getCompany(),user.getCompany().getCompanyName()のいずれの値もnullになる可能性がある場合に、所属する会社名を出力する処理を、Optionalを使って以下のように書くと、userがnullの場合や、会社が未登録、あるいは、会社名が未登録の場合は、"不明"と表示されます。
System.out.printf("所属:%s\n",Optional.ofNullable(user)
.map(u->u.getCompany())
.map(c->c.getCompanyName())
.orElse("不明"));
慣れてきたらメソッド参照を使って以下のように書くようになるでしょう。
System.out.printf("所属:%s\n",Optional.ofNullable(user)
.map(User::getCompany)
.map(Company::getCompanyName)
.orElse("不明"));
もしこれをifをつかって同じことをやると、ここまですっきり書くのは難しいでしょう。3項演算子を使っても冗長になってわかりにくいものになるのも想像できるでしょう。
上記のように値の取り出しを連鎖的に行う場合に、null判定なしに記述できるのが、Optionalの便利な点です。
値の取り出しだけでなく、値の変換を伴う場合もOptionalは便利です。
user.getUserType()でユーザーの社員区分が数値として取り出せて、Map<Integer,String> typeNameMapを参照して区分名称を得るという場合をみてみましょう。getUserType()がnullを返す可能性と、区分がtypeNameMapに存在しない場合があると想定します。typeNameMapに該当する区分があればその区分名を、なければ"区分なし"と表示します。
System.out.printf("社員区分:%s\n",Optional.ofNullable(user)
.map(u->u.getUserType())
.map(t->typeNameMap.get(t))
.orElse("区分なし"));
このように処理の結果の値を使って次の値を得るような場合も便利です。
判定を行う場合の例を見てみましょう。
userに趣味を示すgetHobby()があり、これをみて読書が好きかどうか判定する場合を考えます。
次の処理は、getHobby()が"読書"を返す場合はtrueを返し、そうでない場合はfalseを返し、userがnullあるいは、getHobby()がnullの場合はfalseを返します。
System.out.printf("趣味が読書:%b\n",Optional.ofNullable(user)
.map(u->u.getHobby())
.map(h->h.equals("読書"))
.orElse(false));
続いて、趣味がある場合にだけ、読書好きかどうかのフラグを設定する場合を考えましょう。
これもよくある例なのですが、userがnullやgetHobby()がnullの場合はセットをしない点が良い点です。sample03では、userがnullやgetHobby()がnullの場合は、falseが出力されましたので、この違いを確認してください。
Optional.ofNullable(user)
.map(u -> u.getHobby())
.map(h -> h.equals("読書"))
.ifPresent(f -> user.setReading(f));
これはメソッド参照を使うと以下のように書けます。
Optional.ofNullable(user)
.map(User::getHobby)
.map("読書"::equals)
.ifPresent(user::setReading);
さて、ここまで見てきてOptionalは便利だと感じてもらったと思いますが、なんとなくすっきりしない点があるのを感じませんか?
1つ目は、userやgetCompany()などのどこでnullになったのか、わからない点です。
どの値がnullになることを許容されるのかというのは、設計上重要な問題で、これを明確にしないで実装を行うと、いたるところでヌルポが発生するのは自明です。Optionalを使うことでヌルポがなくなったとしても、設計上の問題を解決したことにはなりません。
Optionalを使うときは、ヌルポを握りつぶしてよいのかどうかを、考えてるとよいでしょう。
2つ目は、orElse()に記載した値です。この値を何にするかを考えることは、重要な設計の一つだと思います。値がnullを返した時にどういうふるまいをすべきか、よく考えるべきです。
適切な値を返すだけでなく、値を返さずに、例外を投げることが必要な場合もあるかと思います。
このようにみると、Optionalを使うことで設計の不備やドキュメントの不備が見えてきます。Optionalは、必要なところで使うと便利であるが、必要以上に使うとバグの発見を先送りすることにつながります。
Optionalを使うところでは、設計を確認するチャンスかもしれません。