最近OptionalのorElse
とorElseGet
の区別は社内でちょっと話題になっていました。これらは同じように見えますが、使い方に気をつけなければハマってしまうかもしれません。まあ、まずJavaDocをみてみましょう。
定義
オラクルのドキュメントの中にorElse
の定義は下記のように書かれています。
public T orElse(T other)
存在する場合は値を返し、それ以外の場合はotherを返します。パラメータ:
other - 存在する値がない場合に返される値、nullも可戻り値:
値(存在する場合)、それ以外の場合はother
一方、orElseGet
はこんな感じです。
public T orElseGet(Supplier<? extends T> other)
値が存在する場合はその値を返し、そうでない場合はotherを呼び出し、その呼び出しの結果を返します。パラメータ:
other - Supplier(値が存在しない場合は、これの結果が返される)戻り値:
値(存在する場合)、それ以外の場合はother.get()の結果例外:
NullPointerException - 値が存在せずotherがnullの場合
やはり説明も似ていますね。でも、nullについての説明は違います。前者の説明の中に、nullも可
が入っていますが、後者にはそれがなく、逆にNullPointerException
という例外の説明が入っています。すなわち、Optional.ofNullable(null).orElse(null)
はnullを返してくれますが、Optional.ofNullable(null).orElseGet(null)
は例外をスローしてしまいます。
それはそうでしょう。引数は確実に違うからですね。orElse
の引数は値で、orElseGet
はSupplierというラムダ式を取っています。Supplierのgetメソッドに通じて値を取得するので、Supplier自体がnullになってしまったら、NullPointerExceptionがスローされてもおかしくありません。
遅延実行
でしたら、下記のコードの実施結果は同じように思えますよね。
1 | public class MyTest1 { |
1 | public class MyTest2 { |
実行してみれば、すぐ分かりますが、前者はtest
を出力している一方、後者はtesttest
を出力しています。test
というSupplier変数はラムダ式であることを忘れてはいけません。Cay S. Horstmannが著書Java SE 8 実践プログラミングの中でラムダ式を紹介する時、「すべてのラムダ式の重要な点は、遅延実行(deferred execution)です。」、「ラムダ式を使用する主な理由は、適切な時期までコードの実行を遅延させることです。」と説明しています。上記の違いはまさにそれを証明する実例になります。もしOptional内の値はnullじゃなければ、orElseGet
内の式が評価される必要がありません。その為、System.out.print(result);
が実行されません。当然ながらtest
の出力はありません。
まとめ
上記のようなコードであれば、まだそんなに問題になりませんが、例えば「Optional内の値はnullの場合、データベースにレコードを挿入し、割り当てられたIDを返す」、「Optional内の値はnullの場合、某APIを叩き、データを取ってくる」みたいなロジックを実装する際に、該当ロジックは常に実行されるので、orElse
を使ってはいけません。間違って使ってしまうと、速度が大幅に遅くなってしまう上、データの整合性が取れなくなってしまうでしょう。
要するに、ラムダの「遅延実行」特性をちゃんと覚えようということですね。