java.svg

【Java】OptionalによるNull検査

Java

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が返されます。

Optionalnullの場合は、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を返します。

Optionalnullの場合は、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

例えば、以下のようにmapOptionalに変換しようとすると、ジェネリクスが入れ子になってしまいます。

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));

実際にはこのような使い方はしませんが、どのようなものか覚えておきましょう。