Java:Optional#orElse、orElseGet

2019年5月19日

Optionalにおいて値が無い場合にどうするかなのだが、orElse、orElseGetというのがある。

どちらを使うべきか?

これを検索してみると。「常にorElseGetを使うべき」などとしているところがあるのだが、もちろんそれでも良いのだが、全く「そもそも」論が抜けている。基本的な原理を考えずに、「うまく行くから」という理由でorElseGetを勧めているに過ぎない。例えばこんな例を使っている。

Optional<String>opt = Optional.empty();
String result = opt.orElse(getSomeString());

String getSomeString() {
  return "foo";
}

そして、「optが空でなくてもgetSomeString()が呼び出されてしまうから」と言っているのだが、当たり前だ。

最終的にresultに返される値は、orElseメソッドの実行結果なのである。したがって、orElseの引数は「必ず実行される」。orElseが中で何をやっていようが関係無い。引数に(定数ではなく)式やらメソッド呼び出しやらが記述されていれば、それは必ず実行されるのである。これはorElseの仕様などではなく、Java言語、あるいは他の言語でも全く同じ普遍的な仕様だ。

その一方で、

String result = opt.orElseGet(()->getSomeString());

とした場合、引数に与えられるのはラムダ式であり、必要になるまでは、getSomeString()が呼び出されないという、それだけのことだ。

orElse、orElseGetのカスケーディング

この項目を書いた理由というのは、orElse、orElseGetのカスケーディングをどのようにすべきかなのだが、本題の前に前置きが長くなってしまった。

お題というのはこうである。

  • あるOptionalがあるが、値が空だった場合は、何らかの値を得る努力をする(他メソッド呼び出しなど)。しかしそれでも空の場合がある。
  • 最終的に空の場合でもnullは扱いたくない。

ということである。単純に考えるとこうだ。

Optional<String>opt = Optional.empty();
String result = opt.orElseGet(()->getSomeString());

String getSomeString() {
  return "foo"; // nullが返される場合もある
}

この場合、resultがnullかどうかを判定しなくてはいけない。単純な解決法としては以下だ。

Optional<String> result = Optional.ofNullable(opt.orElseGet(()->getSomeString()));

あまり美しくない。

Stackoverflowにおける議論

Stackoverflowに議論があった。

Java9のorを使う

Java9からはorというメソッドが用意されている。これを使った場合はこうする。

Optional<String>opt = Optional.empty();
Optional<String>result = opt.or(()->getSomeString());

Optional<String>getSomeString() {
  return Optional.of("foo"); // あるいはOptional.empty()
}

もちろんこれは、次々に接続していくこともできるだろう。

opt
  .or(()->...)
  .or(()->...)
  ....

自前のユーティリティを作ってしまう

Java8でも可能な解決策として、次のようなユーティリティを作ってしまう方法がある。

import java.util.*;
import java.util.function.*;

public class Optionals {
    static <T> Optional<T> or(Supplier<Optional<T>>... optionals) {
        return Arrays.stream(optionals)
                .map(Supplier::get)
                .filter(Optional::isPresent)
                .findFirst()
                .orElseGet(Optional::empty);
    }
}

これを使うと、複数のOptionalから最初のものを選択することができる。

Optionals.or(
  ()->opt, 
  ()-> getSomeString(),
  .....
);

他の解決策も提示されているが、個人的にはこの二つで十分かと思う。