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 (foo != null) {
Bar bar = foo.getBar();
if (bar != null) {
Baz baz = bar.getBaz();
if (baz != null) {
return baz.getValue();
}
}
}
return null;
ここは条件を反転してこう書くべきですね。return文が何度も出てくるけどこっちのほうがすっきりしている。
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()
を書くとこうなる。
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 追記]
上の例はラムダ式で書いたが、メソッド参照を使うともっと短く書ける。
return Optional.ofNullable(foo)
.map(Foo::getBar)
.map(Bar::getBaz)
.map(Baz::getValue)
.orElse(null);
比較
return foo?.getBar()?.getBaz()?.getValue()
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();
return Optional.ofNullable(foo)
.map(fo -> fo.getBar())
.map(bar -> bar.getBaz())
.map(baz -> baz.getValue())
.orElse(null);
return Optional.ofNullable(foo)
.map(Foo::getBar)
.map(Bar::getBaz)
.map(Baz::getValue)
.orElse(null);
セーフナビゲーション演算子にはかなわないが、Optionalを使うことで少なくともif文より若干すっきりしたコードになったと思うが、正直なところ微妙である。
慣れの問題もあるかもしれない。