Java8で新たに追加されたクラスにjava.util.Optionalがあります。
Optionalを使用することで、プログラムの堅牢性を高めたり、煩雑な記述を減らすことが期待されます。
##Optionalとは?
Optionalは値をラップし、 その値がnullかもしれない ことを表現するクラスです。
##使い方
メソッドgetHoge()はnullを返す場合があるとします。
これまでなら次のような感じでnullチェックをしていたと思います。
String hoge = getHoge(); // hogeはnullかも
if (hoge != null) { // nullチェック
System.out.println(hoge.length()); // hogeがnullじゃないのでlengthメソッドを呼ぶ
}
nullかもしれない変数hogeのメソッドを呼ぶ場合、事前にnullチェックが必要ですが、実装が漏れてしまうことがあるかもしれません。
これをOptionalを使って書き換えると、以下のようになります。
Optional<String> hogeOpt = Optional.ofNullable(getHoge()); // 値をラップする
hogeOpt.ifPresent(hoge -> System.out.println(hoge.length())); // 値が存在する場合のみ実行
Optionalを使うことで、値が存在しない場合を必然的に意識しつつも、事前のnullチェックなしに記述することができます。nullチェックのif文でブロックのネストが深くなることもありません。
ofNullableメソッドで、引数の値を持つOptional型のオブジェクトを作成します。
ifPresentメソッドは、保持している値が存在している(nullでない)場合だけ、引数のラムダ式(Consumer型)を実行します。
ラムダ式の引数hogeは保持している実際の値です。
ここでは説明の都合上、getHogeを呼び出した結果をOptional化していますが、本来はgetHogeの内部でOptional化して返すほうが効果的です(後述)。
##処理して結果を返す
ifPresentメソッドは値を返しません。
Optionalの値を使った処理の結果、値を返したい場合はmapメソッドを使用します。
Optional<String> hogeOpt = Optional.ofNullable(getHoge());
Optional<Integer> lengthOpt = hogeOpt.map(hoge -> hoge.length());
mapメソッドの戻り値は、ラムダ式の戻り値をラップしたOptionalになります。
hogeOptの値が存在しない場合はラムダは実行されず、値を持たないOptionalオブジェクトを返します。
また、2つのOptinalオブジェクトがともに値を持つ場合のみ処理結果を返すといった場合は、flatMapメソッドを使うべきです。
flatMapがmapと異なるのは、戻り値を自分でOptionalオブジェクトにする必要がある点です。
// opt1とopt2に値が存在する場合のみ、処理結果を返す
Optional<String> opt1 = getHogeOpt();
Optional<String> opt2 = getHogeOpt();
Optional<String> opt3 = opt1.flatMap(str1 ->
opt2.flatMap(str2 -> {
return Optional.of(str1 + str2);
}));
同じことをmapでやろうとすると、戻り値が Optional<Optional<String>>
になってしまいます。
(コード例で使用しているofメソッドについては後述します)
##filter
上記のmapおよびflatMapメソッドは、同名のメソッドがStreamインターフェースでも定義されており同じような使い方をします。
OptionalのメソッドでStreamのメソッドと同名のものには、他にfilterメソッドがあります。
Optional<String> hogeOpt = Optional.ofNullable(getHoge());
Optional<String> filteredOpt = hogeOpt.filter(hoge -> hoge.length() >= 2);
filterの条件に合致する場合は自身(この場合はhogeOpt)を返し、
条件に合わない場合は値を持たないOptionalオブジェクトを返します。
##Optionalから値を取り出す
Optionalから値を取り出すときは、主にorElseまたはorElseGetメソッドを使用することになると思います。
orElseの引数には、Optionalが値を持っていないときのデフォルト値を指定します。
orElseGetの引数も値がないときのデフォルト値を指定しますが、Supplier型のラムダ式で渡します。
Optional<String> hogeOpt = Optional.ofNullable(getHoge());
// orElse
String hoge = hogeOpt.orElse("デフォルト値");
// orElseGet
String hoge2 = hogeOpt.orElseGet(() -> "生成コストの高いデフォルト値");
orElseGetメソッドのデフォルト値はラムダ式を使って指定しているため、実際に必要になるときまで生成されません。
##基本的なメソッド?
of、isPresent、getという、ある意味基本的なメソッドもありますが、下記のように利用場面はやや限定的です。
// Optionalオブジェクトを生成するofメソッドは、引数がnullだとNullPointerExceptionを投げる
Optional<String> hogeOpt = Optional.of(getHoge());
if (hogeOpt.isPresent()) { // 事前にわざわざ値の存在をチェックしている
// 値を取得するgetメソッドは、値が存在していない場合実行時例外を投げる(NoSuchElementException)
String hoge = hogeOpt.get();
System.out.println(hoge);
}
##Optionalの使いどころ
Optionalの主な使いどころは、メソッドの戻り値として使用することです。
従来ならnullを返すメソッドの戻り値をOptional型にすることによって、値が存在しない場合があることを明示することができます。
性質を明示すること自体、安全なプログラムを構築する上で重要です。
その上で、ifPresentメソッドやmapメソッドでやはり安全かつスマートに処理を行うことができます。
一定の指針を決めて、プロジェクトに取り入れてはいかがでしょうか。