30
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaAdvent Calendar 2015

Day 17

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

Last updated at Posted at 2015-12-16

前置き

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

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 の使い方を逸脱してるから上手くいかないんじゃないか」と心の中でささやく声が聞こえたような気がします。

最終的な結論

コメントにも頂いていますが、JDK9 には ifPresentOrElse が追加されるようなので、JDK9 に期待しましょう。
リリースがいつの事になるやら分からんけどな。

(2017-09-24 追記)
無事 JDK9 がリリースされましたので、9 以降を使う(使える)場合は是非 ifPresentOrElse を使いましょう。 sample code

参考

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

30
26
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
30
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?