Javaでは、null参照に対してメソッドやフィールドにアクセスするとNullPointerExceptionが発生する。これを回避するために、従来は多数のif文でnullチェックを行う必要があり、コードの可読性と保守性が低下していた。
if (user != null) {
System.out.println(user.getFullName());
} else {
User defaultUser = new User("Stark", "Tony Stark");
System.out.println(defaultUser.getFullName());
}
Optionalクラスは、このような問題を関数型スタイルで安全かつ簡潔に解決するための標準APIである。
Optionalとは何か
Optional<T>は値をラップするコンテナ型であり、「値が存在する可能性がある」または「値が存在しない(empty)」という状態を明示的に表現する。これにより、呼び出し側にnull処理を強制し、例外のリスクを減らすことができる。
また、map、filter、ifPresentなどのメソッドを提供しており、命令的なnullチェックを関数型チェーンに置き換えられる。
基本的な使用方法
まず、サンプル用のクラスを定義する:
public class Member {
private String id;
private String displayName;
public Member(String id, String displayName) {
this.id = id;
this.displayName = displayName;
}
public String getId() { return id; }
public Optional<String> getDisplayName() {
return Optional.ofNullable(displayName);
}
}
public class MemberRepository {
public Optional<Member> findById(String id) {
if ("spider".equals(id)) {
return Optional.of(new Member("spider", "Peter Parker"));
}
return Optional.empty();
}
}
Optionalの作成
Optional.empty():空のOptionalを生成Optional.of(value):valueがnullでないことを前提にラップ(nullなら例外)Optional.ofNullable(value):valueがnullでも安全にラップ
値の取得とデフォルト処理
get():値を取得(empty時はNoSuchElementException)orElse(defaultValue):値があればそれを、なければ引数のデフォルト値を返す(常に評価)orElseGet(Supplier<? extends T>):empty時のみSupplierを評価してデフォルト値を生成(遅延評価)orElseThrow(Supplier<? extends Throwable>):empty時に指定例外をスロー
MemberRepository repo = new MemberRepository();
Optional<Member> memberOpt = repo.findById("ironman");
// 遅延評価によるデフォルト値生成
Member fallback = memberOpt.orElseGet(() -> new Member("default", "Guest User"));
// 値がなければカスタム例外をスロー
Member required = memberOpt.orElseThrow(() -> new IllegalArgumentException("Member not found"));
条件付き処理
ifPresent(Consumer<? super T>):値が存在する場合に処理を実行ifPresentOrElse(Consumer, Runnable):値あり/なしで異なる処理を指定filter(Predicate<? super T>):条件を満たせば値を保持、満たさなければemptyに
memberOpt.ifPresent(m -> System.out.println("ID: " + m.getId()));
memberOpt.ifPresentOrElse(
m -> System.out.println("Found: " + m.getId()),
() -> System.out.println("No member found")
);
Optional<Member> namedPeter = memberOpt.filter(m -> "Peter Parker".equals(m.getDisplayName().orElse("")));
変換操作:map vs flatMap
mapは通常の変換に使用され、戻り値がOptionalでない場合に適する。
flatMapは、変換結果がすでにOptionalである場合にネストを回避するために使用される。
// map: Optional<Member> → Optional<Optional<String>> にはならない(自動フラット化されない)
Optional<Optional<String>> displayNameNested = memberOpt.map(Member::getDisplayName);
// flatMap: Optional<Member> → Optional<String>
Optional<String> displayNameFlat = memberOpt.flatMap(Member::getDisplayName);
Streamとの連携
Optional.stream()により、OptionalをStreamに変換できる。値があれば1要素のストリーム、なければ空ストリームとなる。
memberOpt.stream()
.map(Member::getId)
.forEach(System.out::println);
Optionalの不適切な使用例
- フィールドとしての使用:シリアライズが複雑になり、メモリオーバーヘッドが増加
- メソッドやコンストラクタのパラメータ:呼び出し側に余計なラッピングを強いる
- コレクションのラッピング:空リストは既に有効な状態であり、
Optional<List>は冗長 - 無条件の
get()呼び出し:安全でなく、例外リスクあり