Help us understand the problem. What is going on with this article?

Java8のOptionalでセーフナビゲーション演算子相当の処理を書く

More than 5 years have passed since last update.

Groovyやその他最近のイケてる言語の一部ではセーフナビゲーション演算子(?.)というものがあるらしい。

残念ながらJavaではこの演算子は使えないが、Java8で導入されたOptionalクラスを使って同等の処理を書いたらどんなコードになるかやってみた。

セーフナビゲーション演算子とは

オブジェクトのメンバを参照する際に、左辺がnullであってもNullPointerExceptionにならずにnullが返ってくる演算子。

普通のメンバ参照
foo = null
return foo.getBar()   // ぬるぽが発生
セーフナビゲーション演算子
foo = null
return foo?.getBar()  // nullが返る

この例だと三項演算子でfoo != null ? foo.getBar() : nullと書いてもいいが、セーフナビゲーション演算子ではオブジェクト階層が深い場合でもシンプルに書ける。

オブジェクト階層が深い場合
foo?.getBar()?.getBaz()?.getValue()

Javaで同等の処理を書いてみる

if文で書くと...

Optionalの前に、先ずは普通にif文で書いてみる。
上で書いたfoo?.getBar()?.getBaz()?.getValue()をif文でバカ丁寧に書くと、マトリョーシカのようなネストしたコードになってしまう。

if文で書いた例1
if (foo != null) {
    Bar bar = foo.getBar();
    if (bar != null) {
        Baz baz = bar.getBaz();
        if (baz != null) {
            return baz.getValue();
        }
    }
}
return null;

ここは条件を反転してこう書くべきですね。return文が何度も出てくるけどこっちのほうがすっきりしている。

if文で書いた例2
if (foo == null) return null;
Bar bar = foo.getBar();
if (bar == null) return null;
Baz baz = bar.getBaz();
if (baz == null) return null;
return baz.getValue();

三項演算子で書くと...

三項演算子で書くと同じメソッドを数回呼ぶ事になってしまうので、メソッドがコストの高い処理の場合だと厳しいかも。

三項演算子で書いた例
return foo == null ? null :
       foo.getBar() == null ? null :
       foo.getBar().getBaz() == null ? null :
       foo.getBar().getBaz().getValue();

NullPointerExceptionをcatch(ry

よい子はマネしないでね。

try {
    return foo.getBar().getBaz().getValue();
} catch (NullPointerException e) {
    return null;
}

Optionalで書くと...

OptionalはJava8で導入されたクラスで、nullの可能性がある値をラップするクラス。
Optionalクラスを使うとnullでない時だけ処理を行う、といった事をシンプルに書くことができる。

詳細は以下を参照。
http://docs.oracle.com/javase/jp/8/api/java/util/Optional.html
http://www.ne.jp/asahi/hishidama/home/tech/java/optional.html
http://qiita.com/shindooo/items/815d651a72f568112910

で、Optionalを使ってfoo?.getBar()?.getBaz()?.getValue()を書くとこうなる。

Optionalで書いた例
return Optional.ofNullable(foo)
        .map(fo -> fo.getBar())
        .map(bar -> bar.getBaz())
        .map(baz -> baz.getValue())
        .orElse(null);

まず1行目のOptional.ofNullable(foo)でfooをラップしたOptionalを生成する。
もしここでfooがnullならば空のOptional(Optional.empty)が返る。

2行目の.map(fo -> fo.getBar())foo.getBar()を呼び出して結果をOptionalでラップするが、もし1行目でfooがnullで空のOptionalの場合にはfoo.getBar()は呼ばれない。
3行目、4行目も同様。

最後の.orElse(null)で結果の値を取りだすが、ここまでの結果が空のOptionalだった場合には引数で渡した値(null)が返る。

以上で、セーフナビゲーション演算子と同様の処理が実現できる。

[2014/10/31 追記]

上の例はラムダ式で書いたが、メソッド参照を使うともっと短く書ける。

メソッド参照を使ってOptionalで書いた例
return Optional.ofNullable(foo)
        .map(Foo::getBar)
        .map(Bar::getBaz)
        .map(Baz::getValue)
        .orElse(null);

比較

セーフナビゲーション演算子
return foo?.getBar()?.getBaz()?.getValue()
if文
if (foo == null) return null;
Bar bar = foo.getBar();
if (bar == null) return null;
Baz baz = bar.getBaz();
if (baz == null) return null;
return baz.getValue();
三項演算子
return foo == null ? null :
       foo.getBar() == null ? null :
       foo.getBar().getBaz() == null ? null :
       foo.getBar().getBaz().getValue();
Optional(ラムダ式)
return Optional.ofNullable(foo)
        .map(fo -> fo.getBar())
        .map(bar -> bar.getBaz())
        .map(baz -> baz.getValue())
        .orElse(null);
Optional(メソッド参照)
return Optional.ofNullable(foo)
        .map(Foo::getBar)
        .map(Bar::getBaz)
        .map(Baz::getValue)
        .orElse(null);

セーフナビゲーション演算子にはかなわないが、Optionalを使うことで少なくともif文より若干すっきりしたコードになったと思うが、正直なところ微妙である。
慣れの問題もあるかもしれない。

tag1216
Qiita戦闘力はキュイレベルです! /作ったもの ◆QiiTrend:https://qiitrend.herokuapp.com/ ◆Qiiner:https://qiiner.tag1216.net/ ◆Qiitaでいいねしたら草生えるページ:https://qiiner.tag1216.net/likes-heatmap
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away