で、結局 Optional のインスタンスに対して ifNotPresent 的なことをやりたいときはどうしたらいいんですか?

  • 19
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

前置き

表題の件について、要するに

T t = getT();

if (t != null) {
    t.method();
} else {
    logger.warn("Not found.");
}

的なことを、Optional<T> のときどうすればいいのかがわからずにずっともやもやしてきた。
なので、ちょっと腰を据えて考えてみた。
先に断っておきますが、考えただけなので明確な結論はありません。すいません。

実践例!?

ifPresent

// 以下のコード例で、opt は Optional<T> のインスタンス。

opt.ifPresent(t -> t.method());

ifPresent を使うと、else 節がくっつけられないのでだめ。ifPresent はそもそもそういうものなんだろうとは思うが。

isPresent

if (opt.isPresent()) {
    T t = opt.get();
    t.method();
} else {
    logger.warn("Not found.");
}

言うまでもなく Optional がなかった頃から何も進歩していないのでだめ。null を生で扱っているのと大差ない。
なお、Optional についてぐぐれば、よくない例としてよく出てきて ifPresent を使えと言われる。しかし、else 節はどうするのかについては大抵言及されていない・・・。

map/orElseGet

opt.map(t -> {t.method(); return t;})
    .orElseGet(() -> {logger.warn("Not found."); return null;});

一応、流れるように書けている気がしないでもない。
しかし、map 本来の使い方じゃなさそうな気がひしひしするし、return に無理矢理感が漂う。

Runnable を返す (あるいは、迷走の始まり)

opt.<Runnable>map(t -> () -> t.method())
    .orElse(() -> logger.warn("Not found."))
    .run();

要するに else の時に行いたい「処理」をどう扱うかというのが問題なので、いっそのことその「処理」つまり function を丸ごと扱えばいいんじゃないの?という発想。
いやしかし、else の方はいいとして、else じゃないほうを else に合わせてしまっている本末転倒ぶりがちょっと・・・。
ここまで来ると何をやってるか、何をやりたいかが見た目ですぐにわからない。それはお世辞にも良いプログラミングとはいえない。

utility method (迷走は続く)

// 名前付けは適当ですすいません
public class OptionalUtils {
    public static <T> void ifPresentOrElse(Optional<T> opt, Function<T, Runnable> ifPresent, Runnable orElse){
        opt.map(ifPresent).orElse(orElse).run();
    }
}
OptionalUtils.ifPresentOrElse(opt
    , t -> () -> t.method()
    , () -> logger.warn("Not found."));

わかりにくいなら、わかりやすいインターフェースでくるんでやればいいんじゃない?という発想。この spreadsheet の if 文のようなインターフェースがわかりやすいか、という話はさておいて。
いやしかし、こんなことをしても Function<T, Runnable> の部分の違和感が解消されていないのでなんの解決にもなっていないのであった・・・。

でも、これ処理が分離できているので、使い方によればテストしやすくなってるんじゃないかとふと思った。

wrapping

class OptionalEx<T> {

    private Optional<T> opt;

    private OptionalEx(Optional<T> opt) {
        if (opt == null) {
            opt = Optional.empty();
        } else {
            this.opt = opt;
        }
    }

    public static <T> OptionalEx<T> of(Optional<T> opt) {
        return new OptionalEx<>(opt);
    }

    public OptionalEx<T> ifPresent(Consumer<T> c){
        opt.ifPresent(c);
        return this;
    }

    public OptionalEx<T> ifNotPresent(Runnable r){
        if(!opt.isPresent()){
            r.run();
        }
        return this;
    }
}
OptionalEx.of(opt)
    .ifPresent(t -> t.method())
    .ifNotPresent(() -> logger.warn("Not found."));

我に返って、べたにラッピングで実装。
やりたいことには近づいている気はするが・・・。たかだか else 節のためにここまでしないといけないのか!?
Optional をさらに包むとかちょっと気持ち悪い・・・。

結論のような何か

仮の結論

結局、どうしたらいいんですかね?
なんだかんだ言って、このケースは isPresent が無難なんですかね? えー・・・。

真の?結論

なお、割と早いうちに、「そもそも Optional の使い方を逸脱してるから上手くいかないんじゃないか」と心の中でささやく声が聞こえたような気がします。

参考

http://stackoverflow.com/questions/23773024/functional-style-of-java-8s-optional-ifpresent-and-if-not-present
http://stackoverflow.com/questions/29235982/how-to-execute-logic-on-optional-if-not-present

この投稿は Java Advent Calendar 201517日目の記事です。