Article
☕ JavaのorElseとorElseGetの違い:不要な処理と副作用を避ける
JavaのOptionalには、値がない場合の代替値を返すためのorElseとorElseGetがあります。
見た目は似ていますが、実務では代替値を作る処理がいつ評価されるかが重要です。
結論
- 代替値が定数・安価な生成なら
orElse - DBアクセス、外部API、重い計算、副作用を含む処理なら
orElseGet
ポイントは、orElseの引数は通常のJavaメソッド引数なので、Optionalに値が入っていても先に評価されることです。
orElseは値があっても代替値を作る
Optional<String> name = Optional.of("Alice");
String result = name.orElse(loadDefaultName());
この場合、resultは"Alice"ですが、loadDefaultName()自体は呼ばれます。
private static String loadDefaultName() {
System.out.println("default loaded");
return "Guest";
}
出力は次のようになります。
default loaded
値が存在するのに、代替値生成処理は実行されています。
orElseGetは必要なときだけ実行する
同じ処理をorElseGetにすると、Supplierは値がないときだけ評価されます。
Optional<String> name = Optional.of("Alice");
String result = name.orElseGet(() -> loadDefaultName());
この場合、loadDefaultName()は呼ばれません。
Optional.empty()のときだけ実行されます。
Optional<String> name = Optional.empty();
String result = name.orElseGet(() -> loadDefaultName());
DBアクセスをorElseへ入れると危険
例えばキャッシュからユーザーを探し、無ければDBから取得する処理を考えます。
User user = cache.find(userId)
.orElse(repository.findById(userId));
この書き方では、キャッシュにユーザーがいてもrepository.findById(userId)が先に実行されます。
つまり、本来不要なDBアクセスが毎回発生する可能性があります。
遅延実行したい場合はorElseGetを使います。
User user = cache.find(userId)
.orElseGet(() -> repository.findById(userId));
外部APIやログなどの副作用にも注意
代替値生成が単なる計算ではなく、副作用を持つ場合はさらに注意が必要です。
String token = cachedToken
.orElse(issueNewToken());
issueNewToken()が新しいトークンを発行する処理なら、キャッシュ済みトークンが存在していても発行処理が走ります。
この場合もorElseGetが適しています。
String token = cachedToken
.orElseGet(() -> issueNewToken());
定数ならorElseで十分
一方、次のような固定値ならorElseで問題ありません。
String displayName = optionalName.orElse("Guest");
"Guest"を用意するコストはほぼ無視でき、副作用もありません。
読みやすさの面でもこのケースで無理にorElseGet(() -> "Guest")とする必要はありません。
メソッド参照も使える
引数のないメソッドなら、ラムダの代わりにメソッド参照も使えます。
String value = optional.orElseGet(this::loadDefault);
private String loadDefault() {
return "default";
}
nullを返すSupplierは避ける
orElseGetのSupplierは型としてはnullを返すこともできます。
ただし、Optionalを使っているのに最終結果でnullを戻すと、呼び出し側のnull安全性が分かりにくくなります。
String value = optional.orElseGet(() -> null);
特別な理由がなければ、明示的なデフォルト値を返すか、値が必須ならorElseThrowを検討した方が意図が伝わりやすくなります。
String value = optional.orElseThrow(
() -> new IllegalStateException("value is required")
);
性能差だけの問題ではない
orElseとorElseGetの違いは「どちらが高速か」だけではありません。
代替値生成処理に次のようなものが含まれると、動作そのものが変わります。
- DB更新
- 外部API呼び出し
- ファイル生成
- ID採番
- メール送信
- ログや監査イベントの記録
そのため、代替値生成処理に副作用があるかも判断材料にします。
使い分け
| ケース | 推奨 |
|---|---|
| 文字列や定数を返す | orElse |
| 軽い値生成 | orElseでもよい |
| DBアクセス | orElseGet |
| 外部API | orElseGet |
| 重い計算 | orElseGet |
| 副作用のある処理 | orElseGet |
| 値が無いこと自体が異常 | orElseThrow |
まとめ
orElseとorElseGetの違いは、代替値の評価タイミングです。
orElse(other):otherは通常の引数として先に評価されるorElseGet(supplier): 値がないときだけSupplierを実行する- 重い処理や副作用を含む場合は
orElseGet - 単純な定数なら
orElseで十分 - 値が必須なら
orElseThrowも検討する
Optionalを使うときは「値があるかどうか」だけでなく、代替値を作る処理がいつ実行されるかまで見ると、不要な処理や意図しない副作用を避けやすくなります。
参考
- Oracle Java API: Optional