Kotlin Advent Calendar 2021の7->11日目の記事です

概要

safe call(?.)とelvis(?:)でNullableを上手く処理するパターン

Kotlin初心者が引っかかりやすいのでメモしておく

前提

スコープ関数 let

対象に引数の無名関数を適用する

99.let { it.toString().first() } // => "9"

演算子 safe call ?.

対象がnot nullであれば右辺のメソッドを呼び出し、nullであれば何もせずnullを返す

イメージ

if (target != null) {
    target.method()
} else {
    null
}

REPL

var target: Int? = 99
target?.toString() // => "99"
target = null
target?.toString() // =>  null

演算子 elvis ?:

対象がnot nullであればその値をそのまま返し、nullであれば右辺を返す

Nullableにデフォルト値を設定するようなイメージ

イメージ

if (target != null) {
    target
} else {
    right
}

REPL

var target: String? = "dummy"
target ?: "target is null" // => "dummy"

target = null
target ?: "target is null" // => "target is null"

実際の適用例

以下のような処理を想定する

enum class Level(val code: Int) {
    HIGH(1),
    LOW(2),
    NONE(0)
}

val result =
    if (target != null) {
        if (target > 15) {
            Level.HIGH
        } else {
            Level.LOW
        }
    } else {
        Level.NONE
    }
}

この処理に let ?. ?: を適用すると一行で書ける

val result = target?.let { if (it > 15) Level.HIGH else Level.LOW } ?: Level.NONE

1段階ずつ演算子の処理を追うと以下のようになる

val target: Int? = 99
target?.let { if (it > 15) Level.HIGH else Level.LOW } ?: Level.NONE
// => 99?.let { if (it > 15) Level.HIGH else Level.LOW } ?: Level.NONE
// => 99.let { if (it > 15) Level.HIGH else Level.LOW } ?: Level.NONE
// => (if (99 > 15) Level.HIGH else Level.LOW) ?: Level.NONE
// => Level.HIGH ?: Level.NONE
// => Level.HIGH
val target: Int? = null
target?.let { if (it > 15) Level.HIGH else Level.LOW } ?: Level.NONE
// => null?.let { if (it > 15) Level.HIGH else Level.LOW } ?: Level.NONE
// => null ?: Level.NONE
// => Level.NONE

感想

初見だと抽象的で認知的な負荷が高いが、慣れるとJavaのnullを簡潔で柔軟に処理できる優れた設計であることが分かる