Java
Z
信頼度ランク
| S | 公式ソース確認済み |
| A | 成功実績多数・失敗例少数 |
| B | 賛否両論 |
| C | 動作未確認・セキュリティリスク高 |
| Z | 個人所感 |
JavaのOptionalを正しく使う — アンチパターンと実践ガイド
Optional の正しい使い方と、isPresent()+get()を使うアンチパターン、map/flatMap/orElseGet/ifPresentOrElseの使い方を解説します。
一言結論
OptionalはisPresent()+get()の組み合わせで使うと従来のnullチェックと変わらず、map/flatMap/orElseGetのメソッドチェーンで使ってこそ「null安全な変換パイプライン」という本来の価値が発揮される。
Optional とは
null を返すかもしれないメソッドの返り値をラップするコンテナです。Java 8 で導入されました。
// null を返す可能性があるメソッド(旧来のやり方)
public User findUser(int id) {
return database.find(id); // 見つからなければ null
}
// Optional を使う(推奨)
public Optional<User> findUser(int id) {
return Optional.ofNullable(database.find(id));
}
Optional の作り方
// 値あり
Optional<String> opt = Optional.of("hello");
// null かもしれない値をラップ
Optional<String> opt = Optional.ofNullable(value); // null なら empty
// 空の Optional
Optional<String> empty = Optional.empty();
よくあるアンチパターン
NG1: isPresent() + get() の組み合わせ
// NG: Optional の意味がない
Optional<User> userOpt = findUser(id);
if (userOpt.isPresent()) {
User user = userOpt.get();
System.out.println(user.getName());
}
// OK: ifPresent を使う
findUser(id).ifPresent(user -> System.out.println(user.getName()));
NG2: Optional を引数に使う
// NG: 引数を Optional にしない
public void process(Optional<String> name) { ... }
// OK: 引数はnullチェックかオーバーロードで対応
public void process(String name) {
Objects.requireNonNull(name);
// ...
}
NG3: Optional をフィールドに持つ
// NG: シリアライズで問題が起きる
public class User {
private Optional<String> email; // NG
}
// OK: @Nullable アノテーションを使う
public class User {
private @Nullable String email;
}
NG4: コレクションを Optional で返す
// NG: 空コレクションを返せばよい
public Optional<List<User>> findAll() { ... }
// OK: 空のリストを返す
public List<User> findAll() {
return Collections.emptyList();
}
正しい使い方
orElse / orElseGet / orElseThrow
// orElse: デフォルト値(常に評価される)
String name = findUser(id).map(User::getName).orElse("Anonymous");
// orElseGet: デフォルト値を遅延評価(Supplier で渡す)
String name = findUser(id).map(User::getName)
.orElseGet(() -> generateDefaultName()); // 必要な時だけ実行
// orElseThrow: 値がなければ例外をスロー(Java 10以降引数なしも可)
User user = findUser(id).orElseThrow(
() -> new UserNotFoundException("User not found: " + id)
);
// Java 10以降: 引数なしは NoSuchElementException
User user = findUser(id).orElseThrow();
orElse vs orElseGet:
デフォルト値の生成コストが高い場合は orElseGet を使います。orElse("定数") のように定数ならどちらでも同じです。
map / flatMap / filter
// map: 値があれば変換する
Optional<String> name = findUser(id).map(User::getName);
// 複数の変換をチェーンする
Optional<String> city = findUser(id)
.map(User::getAddress) // User → Address
.map(Address::getCity); // Address → String
// flatMap: Optional を返すメソッドをチェーン
// map だと Optional<Optional<Address>> になるが flatMap だと Optional<Address>
Optional<String> city = findUser(id)
.flatMap(user -> user.getOptionalAddress()) // Optional<Address> を返す
.map(Address::getCity);
// filter: 条件を満たす場合だけ値を残す
Optional<User> adultUser = findUser(id)
.filter(user -> user.getAge() >= 18);
ifPresent / ifPresentOrElse
// ifPresent: 値がある場合だけ処理
findUser(id).ifPresent(user -> sendWelcomeEmail(user));
// ifPresentOrElse(Java 9以降): ある場合とない場合で分岐
findUser(id).ifPresentOrElse(
user -> sendWelcomeEmail(user),
() -> log.warn("User not found: " + id)
);
or(Java 9以降)
// Optional が空の場合に別の Optional を返す
Optional<User> user = findInLocalCache(id)
.or(() -> findInDatabase(id))
.or(() -> findInRemote(id));
stream(Java 9以降)
// Optional を Stream に変換(値があれば1要素、なければ0要素)
List<User> users = userIds.stream()
.map(this::findUser) // Stream<Optional<User>>
.flatMap(Optional::stream) // Stream<User>(空のOptionalは除外)
.collect(Collectors.toList());
まとめ
| 状況 | 使うメソッド |
|---|---|
| デフォルト値を返したい | orElse / orElseGet |
| 例外を投げたい | orElseThrow |
| 変換したい | map / flatMap |
| 条件でフィルタ | filter |
| 副作用の処理 | ifPresent / ifPresentOrElse |
| ストリームで処理 | stream() |
Optional は「返り値の型」として使うのが正しい使い方です。フィールドや引数には使わず、メソッドの返り値の型として使いましょう。