JavaのOptionalで学ぶnull安全 — nullチェックだらけのコードを綺麗にする方法
はじめに
Javaを書いていると、避けて通れないのがNullPointerException(通称NPE)です。
String name = user.getName();
System.out.println(name.length()); // userがnullだったら? → NPE!
これを防ごうとすると、こうなりがちです。
if (user != null) {
String name = user.getName();
if (name != null) {
System.out.println(name.length());
}
}
nullチェックのネスト地獄。読みにくいし、書くのも面倒。
Java 8で導入されたOptionalを使えば、このnullチェックをもっとスマートに書けます。
Optionalとは何か
Optionalは、「値があるかもしれないし、ないかもしれない」を表現するクラスです。
Optional<String> name = Optional.of("田中"); // 値あり
Optional<String> empty = Optional.empty(); // 値なし
Optional<String> nullable = Optional.ofNullable(null); // nullならempty
「nullかもしれない値」をOptionalで包むことで、nullを直接触らずに安全に扱えるのがポイントです。
基本的な使い方
1. isPresent() / isEmpty() — 値があるか確認
Optional<String> name = Optional.ofNullable(getName());
if (name.isPresent()) {
System.out.println(name.get());
}
// Java 11以降
if (name.isEmpty()) {
System.out.println("名前がありません");
}
ただし、これだとnullチェックとあまり変わりません。Optionalの真価は次からです。
2. ifPresent() — 値があるときだけ処理する
Optional<String> name = Optional.ofNullable(getName());
name.ifPresent(n -> System.out.println("名前: " + n));
nullチェックのif文が消えて、1行で安全に処理できます。
3. orElse() / orElseGet() — デフォルト値を返す
String name = Optional.ofNullable(getName())
.orElse("名無し");
// 重い処理でデフォルト値を生成する場合はorElseGet
String name = Optional.ofNullable(getName())
.orElseGet(() -> generateDefaultName());
4. map() — 値を変換する
Optional<String> upperName = Optional.ofNullable(getName())
.map(String::toUpperCase);
値がnullなら変換はスキップされ、Optional.empty()が返ります。チェーンで安全に変換できます。
5. flatMap() — Optionalを返すメソッドをつなげる
Optional<String> city = Optional.ofNullable(user)
.flatMap(User::getAddress) // AddressがOptionalで返る
.flatMap(Address::getCity); // CityもOptionalで返る
ネストしたOptionalをフラットにつなげてくれます。
実務でよくあるパターン
Before: nullチェックだらけ
public String getUserCity(User user) {
if (user != null) {
Address address = user.getAddress();
if (address != null) {
String city = address.getCity();
if (city != null) {
return city;
}
}
}
return "不明";
}
After: Optionalでスッキリ
public String getUserCity(User user) {
return Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("不明");
}
ネストが消えて、処理の流れが一目でわかるようになりました。
Optionalを使う時の注意点
やってはいけないこと
// NG: get()を直接呼ぶ(NoSuchElementExceptionの危険)
Optional<String> name = Optional.empty();
name.get(); // 例外が飛ぶ!
// NG: フィールドにOptionalを使う
public class User {
private Optional<String> name; // これはNG
}
// NG: メソッドの引数にOptionalを使う
public void greet(Optional<String> name) { // これもNG
}
Optionalの正しい使いどころ
| 使い方 | OK / NG |
|---|---|
| メソッドの戻り値 | OK(本来の使い方) |
| フィールド | NG(Serializableではない) |
| メソッドの引数 | NG(呼び出し側が複雑になる) |
| コレクションの要素 | NG(空のコレクションで代替) |
Optionalは「戻り値がnullかもしれない」ことを明示するための道具です。
Stream APIとの組み合わせ
Java 9以降、OptionalとStream APIを組み合わせるとさらに便利です。
// Optional → Stream変換(Java 9+)
List<String> names = users.stream()
.map(User::getName) // Optional<String>が返る場合
.flatMap(Optional::stream) // 値があるものだけ取り出す
.collect(Collectors.toList());
まとめ
| メソッド | 用途 |
|---|---|
ofNullable() |
null許容のOptionalを作成 |
ifPresent() |
値があるときだけ処理 |
orElse() |
デフォルト値を指定 |
map() |
値を変換 |
flatMap() |
Optional同士をつなげる |
Optionalを使うと:
- nullチェックのネストが消える
- 「この値はnullかも」が型で伝わる
- NPEのリスクが減る
おわりに
Optionalは最初「nullチェックと何が違うの?」と思いがちですが、使い慣れるとコードの安全性と可読性が格段に上がります。
特にmap()とorElse()の組み合わせは実務で頻出するので、ぜひ手を動かして試してみてください。
nullとの戦いが少し楽になるはずです。