Optional.orElse / orElseGet は「無いときどうするかを決めるメソッド」
Optional は「あるかもしれないし、ないかもしれない値」を入れる箱でした。
では、その箱が「空だったとき」にどうするかを決めるのが orElse / orElseGet です。
雰囲気でいうと、
orElse(T other)
「中身が無ければ、この値を代わりに返して」
orElseGet(Supplier<? extends T> supplier)
「中身が無ければ、この処理を実行して“代わりの値”を作って返して」
という違いです。
パッと見似ていますが、
「代わりの値の作り方(すぐ作るか、必要になってから作るか)」が重要な差になります。
Optional.orElse の基本:もう手元にある“予備の値”を渡す
使いどころのイメージ
orElse は「もう手元に予備の値があるとき」に使うのが自然です。
例えば、「ユーザ名がなければ "ゲスト" にしたい」というケース。
Optional<String> optName = findUserName(userId); // Optional<String>
String name = optName.orElse("ゲスト");
System.out.println(name);
JavaoptName に値が入っていればその値が返り、
空だった場合は "ゲスト" が返ります。
イメージとしては:
- ある → そのまま使う
- ない → orElse に渡した値を使う
というシンプルな動きです。
orElse は「引数を必ず評価する」
ここがとても大事なポイントです。
orElse に渡した式は、「Optional に中身があるかどうかに関係なく」
必ず先に評価されてから 渡されます。
例えば、次のコードを見てください。
public String createDefaultName() {
System.out.println("createDefaultName() 実行");
return "デフォルト";
}
Optional<String> opt = Optional.of("Taro");
String name = opt.orElse(createDefaultName());
System.out.println("name = " + name);
Java出力はこうなります。
createDefaultName() 実行
name = Taro
JavaOptional に "Taro" が入っていても、createDefaultName() は呼ばれてしまいます。
呼ばれた結果 "デフォルト" が生成されますが、結局それは捨てられて "Taro" が採用されます。
つまり、orElse の引数は
「中身があってもなくても、とりあえず一回は計算される」
ということです。
Optional.orElseGet の基本:必要になったときだけ“遅延評価”する
orElseGet は関数(ラムダ)を渡す
orElseGet は「中身が無かったときだけ代わりの値を作ってほしい」場合に使います。
さっきの例を orElseGet で書き直します。
String name = opt.orElseGet(() -> createDefaultName());
Javaまたはメソッド参照で:
String name = opt.orElseGet(this::createDefaultName);
Javaここでは () -> createDefaultName() という「引数を取らずに値を返す関数(Supplier)」を渡しています。
動きとしては:
- Optional に値がある → その値を返し、ラムダは実行されない
- Optional が空 → ラムダが実行され、その戻り値が採用される
となります。
orElseGet の「本当に必要なときだけ実行される」メリット(重要)
先ほどの例を orElseGet に変えると、出力はこうなります。
Optional<String> opt = Optional.of("Taro");
String name = opt.orElseGet(() -> createDefaultName());
System.out.println("name = " + name);
Javaこの場合、createDefaultName() は呼ばれません。
出力は
name = Taro
だけになります。
逆に opt が空のときは、
Optional<String> opt = Optional.empty();
String name = opt.orElseGet(() -> createDefaultName());
System.out.println("name = " + name);
Java出力は
createDefaultName() 実行
name = デフォルト
のようになります。
ここから分かることは、
orElse → 代わりの値を「先に作ってから」渡すorElseGet → 代わりの値を「必要になったときだけ作る」
という違いです。
orElse と orElseGet の使い分け方
代わりの値を作るコストが軽いなら orElse で十分
例えば、「単純な固定文字列」を返したいだけなら、
わざわざラムダを書くまでもなく orElse で構いません。
String name = optName.orElse("ゲスト");
Java"ゲスト" の生成コストはほぼゼロですし、orElseGet(() -> "ゲスト") と書くと、むしろ読みづらくなります。
このように、「代わりの値の生成が軽くて、書きやすい」場合は
シンプルに orElse で OK です。
重い処理や副作用があるなら orElseGet を選ぶ(重要)
一方で、「代わりの値を作る処理が重い」「副作用がある」場合は orElseGet を使うべきです。
例えば、「デフォルト値を DB や設定ファイルから読み込む」ようなケース。
Optional<String> optConfig = findConfig("timeout");
// 悪い例(どちらにせよ loadDefaultConfig() が呼ばれてしまう)
String value = optConfig.orElse(loadDefaultConfig());
// 良い例(空のときだけ loadDefaultConfig() が実行される)
String value = optConfig.orElseGet(() -> loadDefaultConfig());
JavaloadDefaultConfig() が
- ファイル I/O をする
- ネットワークにアクセスする
- ログを出力する
などの「重い/副作用のある処理」だった場合、
値があるのに毎回呼ばれてしまうのは無駄です。
こういうときに、「必要になったときだけ実行する」orElseGet の遅延評価が効いてきます。
もう一歩:orElseGet は「後回しにできる if」のようなもの
if 文で書くとどう見えるか
Optional を使わずに if で書くと、こんな形です。
String value;
if (result != null) {
value = result;
} else {
value = loadDefault();
}
Javaこれを Optional にすると、次のような対応になります。
String value = Optional.ofNullable(result).orElseGet(() -> loadDefault());
Javaif 版では、result != null なら loadDefault() は呼ばれません。
orElseGet 版も同じく、Optional が空のときだけ loadDefault() が実行されます。
orElse を使っていた場合は、こうなってしまいます。
String value = Optional.ofNullable(result).orElse(loadDefault());
Javaこれは if で書き直すと、こういう状態です。
String tmp = loadDefault(); // 先に必ず呼ばれる
String value;
if (result != null) {
value = result;
} else {
value = tmp;
}
Java「結局使わないかもしれないのに、先に loadDefault を実行してしまう if」になっているイメージです。
orElse / orElseGet の違いは、この if で考えると感覚として掴みやすくなります。
まとめ:初心者としての orElse / orElseGet の基準
最後に、頭に残してほしいポイントを整理します。
orElse(value) は
「もう手元にあるシンプルな代替値」を使うときに向いている。
その代わり、引数は毎回評価される(重い処理は NG)。
orElseGet(() -> value) は
「代替値を“計算して作る”必要がある」場合に向いている。
Optional が空のときだけ、その計算を実行する(遅延評価)。
イメージとしては、
- 軽くて固定 → orElse
- 重い・副作用あり・計算が必要 → orElseGet
と覚えておくと、だいたい困りません。
