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.4 | error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'String?'. |
| kotlinc 1.9 | error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? |
| Gradle | e: 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 になる。
よくある原因
String?やList<String>?のように、宣言した型がnullを許している。
引数や戻り値の型に?が付いている場合も同じである。nullを許す型を返す API の戻り値をそのまま使っている。firstOrNull()・readlnOrNull()・Mapの添字(m["a"])は、値が無いとnullを返す。