前置き
表題の件について、要するに
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