この記事はAndroid Advent Calendar 2018の15日めです。
え?前回の日記は16日目だったのになぜ 今日は15日目かだって? それは言わないお約束です。
Swiftには次のように書くことで、
hogeが非nullだった場合はif内の処理、nullだった場合はelseの処理を行う仕組みがあります。
if let hoge = hoge.val {
// hogeがnullでないときだけ実行
} else {
// hogeがnullのときだけ実行
}
似たようなことをKotlinでやろうとするとこんな感じでやりがち
hoge?.let{hoge->
// hogeがnullでないときだけ実行
}
定形ですね。
ところが、この方法ではelseを指定することが出来ません。
そこで、
ついうっかりエルビス演算子を使ってしまいがちです。
hoge?.let{hoge->
// hogeがnullでないときだけ実行
}?:run{
// hogeがnullのときだけ実行
}
試しに実行してみましょう。
hogeがnullのとき
var hoge: String? = null
hoge?.let{
System.out.println("not null")
} ?: run{
System.out.println("null")
}
// null
hogeが非nullのとき
var hoge: String? = "hogehoge"
hoge?.let{
System.out.println("not null")
} ?: run{
System.out.println("null")
}
// not null
良さそうです。
嘘です 良くないです。
問題
この方式には明確な問題点があります。 エルビス演算子は左辺がnullの場合のみ右辺を評価します。
?.はnullではないときだけ以降の処理を行い、nullのときはnullを返します。 なので一見うまく動いているように見えます。
ところが、問題はletの戻り値です。
nullのアンラップのためにletを使っていると忘れそうになりますが、letは最後の処理結果を戻り値として戻します。
それが何故問題になるかというと、
こういう処理を書くとバグります。
hogeは非nullだけれど nullとも出力される。
var hoge: String? = "hogehoge"
hoge?.let{
System.out.println("not null")
null
} ?: run{
System.out.println("null")
}
// not null
// null
letの最後でnullがあるのでエルビス演算子の左辺がnullとなってしまい右辺が評価されます。
ここまであからさまにnullを書くことはないかもしれませんが、最後の処理がnullを戻す処理だった場合は同様の問題が発生します。
対策
冒頭のタイトルに有るようにletではなくalsoを使いましょう。
hoge?.also{
System.out.println("not null")
null
} ?: run{
System.out.println("null")
}
// not null
letとalsoの違いは戻り値の違いでletはlet内で最後に評価した値を戻り値とするのに対してalsoはその元になったオブジェクト(ここではhoge)の値が戻ります。
hogeが非nullの場合はalsoの戻り値もnullになりえないのであとのrunを誤爆する恐れがありません
あとがき
そもそも if elseを使ったほうが見やすいのでは? という話もある。
if(hoge != null){
System.out.println("not null")
}else{
System.out.println("null")
}