28
25

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.

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

Last updated at Posted at 2014-10-09

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文より若干すっきりしたコードになったと思うが、正直なところ微妙である。
慣れの問題もあるかもしれない。

28
25
0

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
28
25

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?