オプショナル型の値が入っていたら何か処理をする
Kotlin には let
という構文がある。Swift だと if-let
文が相当するし Java だと Optional#ifPresent()
だ。
あるオプショナル型の変数に値が入っていたら何かする、といった具合に使用する:
val a: String? = null
a?.let { print(it) } // let ブロック内で it で optional が外された形で使用可能
a?.let { print(it) } ?: print("nothing") // elvis 演算子と組み合わせる事で null の場合の処理も記述可能
この a?.let
が曲者な気がする。
typo するとバグの元になりそう
上記の例で以下のように間違えると意図した結果にならない:
val a: String? = null
a.let { print(it) } // ? が無いので a が null でも print されてしまう!!
a?.let { print(it) } // ? があるので正しく print されない
これでうっかりハマって暫く気づかなかった……。
ちなみに let
のブロック内で it
のメソッド呼び出しをしようとするとオプショナル型がアンラップされていないのに気づいてコンパイルエラーになる:
val a: String? = null
a.let { it.toInt() } // オプショナル型 it への安全でないメソッド呼び出しでコンパイルエラー
a?.let { it.toInt() } // これは OK (it.toInt() は実行されない)
Swift や Java ではこういうことはない
Swift の if-let
ではこういうことはないし Java だとそもそも Optional 型でないと ifPresent
メソッドをコールできないのでこういうことはない。
カラクリ
ソースを見ると実は Kotlin の let
は言語構造的なものではなく拡張メソッド (エクステンション) を使用して以下のように定義されている:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R = block(this) // 渡された関数オブジェクトを実行するだけ!!
この定義を見ると何てことはなく、ただ単に引数に渡された関数オブジェクトを実行するだけの拡張メソッドである。
つまり String?
型として let
を呼べるし、呼ぶと上記 block
の型は (String?) -> R
となるだけのことだった。
let
は実は以下の省略形と書けば理解が進む気がする:
val a: String? = null
a?.let { print(it) } // せいかい. 実は (String) -> Unit
a?.let { b: String -> print(b) } // 冗長な書き方. 上と同じ
a.let { print(it) } // 間違い. 実は (String?) -> Unit
a.let { b: String? -> print(b) } // 冗長な書き方. 上と同じ