できない.dev

Kotlin「Only safe (?.) or non-null asserted (!!.) calls」の原因と直し方

Only safe (?.) or non-null asserted (!!.) calls は、?. か ?: で null を扱えば直る。
String? のように null を許す型のまま、メンバーを直接呼ぶとコンパイルエラーになる。

公開:

要約

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver は、null を許す型の値でメンバーを直接呼んだときのコンパイルエラーである。?. と ?: で null の場合を書くか、if で null を除けば直る。

次のコードでは、name の型が String? なので name.length が通らない。

fun main() {
    val name: String? = System.getenv("NAME")
    println(name.length)
}

表示は、コンパイラの版と実行方法で少し変わる。

実行したもの表示
kotlinc 2.4error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'.
kotlinc 1.9error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?
Gradlee: file:///w1/src/main/kotlin/App.kt:3:17 Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'.

解決策

1. ?. と ?: で null の場合を書く

?. は、値が null なら呼び出さずに null を返す。?: は、左側が null のときの値を決める。

fun main() {
    val name: String? = System.getenv("NAME")
    println(name?.length)      // NAME が無ければ null
    println(name?.length ?: 0) // NAME が無ければ 0
    name?.let { println(it.uppercase()) }
}

if で null を除くと、そのブロックの中では String として扱える(スマートキャスト)。

if (name != null) {
    println(name.length)
}

var のプロパティは、確かめたあとに別の処理で書き換えられる可能性があるので、if で確かめても smart cast to 'String' is impossible になる。
ローカル変数に受けると通る。

class User(var name: String?)
 
fun show(u: User) {
    val n = u.name
    if (n != null) {
        println(n.length)
    }
}

2. null を返さない API に替える

値が必ずあると分かっているなら、null を返さない API を使う。Map の getValue() は null を許さない型を返し、キーが無ければ例外を投げる。
値が無いときの結果を決めたいなら ?: を使う。

val m = mapOf("a" to "x")
println(m.getValue("a").length)
println(m["b"]?.length ?: 0)

!! はコンパイルを通すだけ

エラー文に出てくる !! を付けるとコンパイルは通るが、値が null のときに実行時の例外で落ちる。null にならないことを説明できる場所以外では使わない。

Exception in thread "main" java.lang.NullPointerException
	at G2Kt.main(g2.kt:3)

Java のメソッドの戻り値は、Kotlin 側で null を許すかどうかが決まっていない型(プラットフォーム型)になる。
注釈の無い Java のメソッドが null を返しても、このコンパイルエラーは出ず、実行時に NullPointerException になる。

よくある原因

  1. String? や List<String>? のように、宣言した型が null を許している。
    引数や戻り値の型に ? が付いている場合も同じである。
  2. null を許す型を返す API の戻り値をそのまま使っている。firstOrNull()・readlnOrNull()・Map の添字(m["a"])は、値が無いと null を返す。