【Java】OptionalによるNull検査
Optional とは
Optional
とは、Java8 で導入された仕組み(クラス)になります。
例えば以下のように、データを取得するメソッドがあるとします。
private Map<String, String> users = new HashMap<String, String>() {
{
put("0001", "Taro");
put("0002", "Jiro");
put("0003", "Saburo");
}
};
public String getName(String id) {
return users.get(id);
}
getName()
は、id
に該当するデータがあればその値を返しますが、ない場合はnull
を返します。
しかし、getName()
の実装を知らなければ、これがnull
を返すかどうかがわかりません。
知らずにgetName().toUpperCase()
などとすると、NullPointerException
がスローされてしまいます。
これを明示的に「null
の可能性がある値」と示すのがOptional
になります。
Optional
から値を取得するには、「null
であるかを検査する」必要があります。
Optional の宣言
Optional
はジェネリクスになっており、検査したい値の型を設定します。
public Optional<String> getName(String id) {
//...
}
値の設定
ofNullable
null
の可能性のある値を設定します。
public Optional<String> getName(String id) {
return Optional.ofNullable(users.get(id));
}
of, empty
of
は、null
でない値を設定します。
値が null の場合は、NullPointerException
がスローされます。
empty
は、値としてnull
を設定します。
public Optional<String>getName(String id) {
if (users.containsKey(id)) {
return Optional.of(users.get(id));
} else {
return Optional.empty();
}
}
上記の例は、ofNullable
の例と同じ結果になります。
値の取得・検査
get
Optional
に設定した値を取得します。
ただし値がnull
の場合は、NoSuchElementException
がスローされます。
Optional<String> opt1 = getName("0001");
String name1 = opt1.get(); // "Taro"
Optional<String> opt2 = getName("0004");
String name2 = opt2.get(); // NoSuchElementException
isPresent
Optional
に設定した値がnull
でないことを検査します。
Optional<String> opt = getName("0001");
if (opt.isPresent()) {
// nullでない場合
} else {
// nullの場合
}
orElse
Optional
に設定した値がnull
でなければその値を、null
であればorElse
で設定した値を取得します。
Optional<String> opt1 = getName("0001");
String name1 = opt1.orElse("No User"); // "Taro"
Optional<String> opt2 = getName("0004");
String name2 = opt2.orElse("No User"); // "No User"
orElseGet
考え方はorElse
と同じですが、orElseGet
にはラムダ式でどのような値を返すかの処理を設定します。
Optional<String> opt1 = getName("0001");
String name1 = opt1.orElseGet(() -> {
//値設定のための処理
return "No User"
}); // "Taro"
Optional<String> opt2 = getName("0004");
String name2 = opt2.orElseGet(() -> {
//値設定のための処理
return "No User"
}); // "No User"
orElseThrow
Optional
に設定した値がnull
でなければその値を取得し、null
であれば指定した例外をスローします。
Optional<String> opt1 = getName("0001");
String name1 = opt1.orElseThrow(() -> new RuntimeException()); // "Taro"
Optional<String> opt2 = getName("0004");
String name2 = opt2.orElseThrow(() -> new RuntimeException()); // RuntimeException
ifPresent
Optional
に設定した値がnull
でない場合の処理をラムダ式で設定します。
引数には設定した値が渡されます。
null
の場合は何も起きません。
Oprional<String> opt = getName("0001");
opt.ifPresent((e) -> {
//nullでない場合の処理
//e: "Taro"
});
値の変換
filter
Optional
に設定した値に、ラムダ式で条件を設けます。
条件を満たす場合はその値が設定されたOptional
が、満たさない場合はnull
を持つOptional
が返されます。
Optional
がnull
の場合は、null
を持つOptional
が返されます。
Optional<String> opt10 = getName("0001");
Optional<String> opt11 = opt10.filter((e) -> e.length < 5); // Optional: "Taro"
Optional<String> opt12 = opt10.filter((e) -> e.length < 4); // Optional: null
Optional<String> opt20 = getName("0004");
Optional<String> opt21 = opt20.filter((e) -> e.length < 5); // Optional: null
map
Optional
に設定した値を、ラムダ式で指定した値に変換します。
結果として、変換した値を持つOptional
を返します。
Optional
がnull
の場合は、null
を持つOptional
が返されます。
Optional<String> opt10 = getName("0001");
Optional<String> opt11 = opt10.map((e) -> e.toUpperCase()); // Optional: "TARO"
Optional<String> opt20 = getName("0004");
Optional<String> opt21 = opt20.map((e) -> e.toUpperCase()); // Optional: null
flatMap
例えば、以下のようにmap
でOptional
に変換しようとすると、ジェネリクスが入れ子になってしまいます。
Optional<String> opt10 = getName("0001");
Optional<Optional<String>> opt11 = opt10.map((e) -> Optional.ofNullable(e));
これを解決するのがflatMap
です。
Optional<String> opt10 = getName("0001");
Optional<String> opt11 = opt10.flatMap((e) -> Optional.ofNullable(e));
実際にはこのような使い方はしませんが、どのようなものか覚えておきましょう。