PC パソコン

Kotlin「Only safe (?.) or non-null asserted (!!.) calls are allowed」の解決方法

Kotlinでコードを記述していると、
「Only safe (?.) or non-null asserted (!!.) calls are allowed」
というエラーが表示されることがあります。

このエラーは、
nullになる可能性があるnullable型の値に対して、
通常の方法でプロパティや関数へアクセスしようとした場合に発生します。

Kotlinでは、
nullを許可する型と許可しない型が明確に区別されているため、
nullable型を使用するときは、
nullの場合を考慮した処理が必要です。

この記事では、Kotlinで
「Only safe (?.) or non-null asserted (!!.) calls are allowed」
が表示された場合に確認したい原因と、
基本的な解決方法を紹介します。

「Only safe (?.) or non-null asserted (!!.) calls are allowed」とは

「Only safe (?.) or non-null asserted (!!.) calls are allowed」は、
nullになる可能性がある値に対して、
通常のプロパティアクセスや関数呼び出しを行っていることを示すエラーです。

例えば、次のようなコードがあるとします。

val text: String? = null

println(text.length)

このコードでは、
text
String?型として定義されています。

String?はnullになる可能性があるため、
text.lengthのように
そのままプロパティへアクセスすることはできません。

nullの場合に
lengthへアクセスすると処理できないため、
Kotlinがコンパイル時にエラーとして検出します。

nullable型の値へアクセスする場合は、
セーフコール演算子?.
nullチェック、
Elvis演算子?:などを使用して、
nullの場合を考慮した処理を記述します。

原因1:nullable型に直接アクセスしている

最も基本的な原因は、
nullable型の変数に対して
.を使って直接アクセスしていることです。

val name: String? = "Taro"

println(name.length)

nameには
"Taro"が入っていますが、
型としてはString?なので、
nullになる可能性があります。

そのため、
Kotlinは通常の
.によるアクセスを許可しません。

セーフコール演算子「?.」を使用する

nullable型の値へ安全にアクセスする基本的な方法が、
セーフコール演算子
?.です。

val text: String? = null

println(text?.length)

textがnullでなければ、
lengthが取得されます。

一方、
textがnullの場合は
lengthへアクセスせず、
式全体の結果がnullになります。

nullable型のプロパティや関数へアクセスするときは、
まず?.で安全に処理できないか確認する方法が基本です。

nullチェックを行ってからアクセスする

値がnullでない場合だけ処理したい場合は、
nullチェックを行ってからアクセスできます。

val text: String? = "Hello"

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

text != nullが成立している範囲では、
Kotlinがtext
nullではない値として扱える場合があります。

この仕組みにより、
?.を使用せずに
text.lengthへアクセスできます。

Elvis演算子「?:」でnullの場合の値を指定する

nullable型の値がnullだった場合に
別の値を使用したい場合は、
Elvis演算子
?:を使用できます。

val text: String? = null

val length = text?.length ?: 0

この場合、
textがnullでなければ
文字列の長さがlengthに入ります。

nullの場合は、
0が使用されます。

nullをその後の処理へ渡したくない場合に使用できます。

「!!」でnon-nullとして扱う方法

Kotlinには、
nullable型の値を強制的にnon-nullとして扱う
!!演算子があります。

val text: String? = "Hello"

println(text!!.length)

この場合、
Kotlinに対して
textはnullではないものとして扱うよう指定しています。

ただし、
実際の値がnullだった場合は、
実行時に例外が発生します。

val text: String? = null

println(text!!.length)

!!を使用するとコンパイルエラーを回避できる場合がありますが、
nullだった場合に実行時エラーが発生する可能性があります。
nullでないことが確実な場合を除き、
セーフコールやnullチェックを優先して検討します。

原因2:関数の戻り値がnullable型になっている

自分でnullable型の変数を宣言していなくても、
関数の戻り値がnullable型であるために
エラーが発生することがあります。

fun getName(): String? {
    return null
}

val name = getName()

println(name.length)

getName()の戻り値は
String?なので、
nameもnullになる可能性があります。

セーフコールを使用する場合は、
次のように記述します。

println(name?.length)

また、
nullの場合の値を指定することもできます。

println(name?.length ?: 0)

原因3:Android APIの戻り値がnullable型になっている

Android開発では、
APIの戻り値がnullable型になっていることが多くあります。

例えば、
Intentから文字列を取得する
getStringExtra()の戻り値は、
値が存在しない可能性を考慮する必要があります。

val name = intent.getStringExtra("name")

println(name.length)

nameがnullになる可能性がある場合、
そのままlengthへアクセスすることはできません。

セーフコールを使用する場合は、
次のように記述します。

println(name?.length)

nullの場合に空文字を使用するのであれば、
次のように記述することもできます。

val name: String = intent.getStringExtra("name") ?: ""

println(name.length)

原因4:findViewByIdなどで取得した値がnullableになっている

AndroidのViewや外部APIから値を取得した場合も、
nullable型として扱われることがあります。

その値に直接アクセスすると、
同様のエラーが表示される場合があります。

値が存在しない可能性がある場合は、
セーフコールやnullチェックを使用します。

val view: View? = findViewById(R.id.sampleView)

view?.visibility = View.VISIBLE

この場合、
viewがnullであれば
visibilityの設定は実行されません。

原因5:Mapから取得した値がnullable型になっている

KotlinのMapから
[]などで値を取得した場合、
指定したキーが存在しない可能性があるため、
戻り値がnullable型になります。

val users = mapOf(
    "user1" to "Taro"
)

val name = users["user1"]

println(name.length)

users["user1"]
nullになる可能性があるため、
nameにそのままアクセスするとエラーになります。

セーフコールを使用する場合は、
次のように記述します。

println(name?.length)

デフォルト値を使用する場合は、
Elvis演算子を組み合わせることもできます。

println(name?.length ?: 0)

原因6:セーフコールの途中で戻り値がnullableになっている

セーフコールを使用すると、
その式の結果もnullable型になることがあります。

val text: String? = "Hello"

val length: Int? = text?.length

textがnullの場合、
text?.lengthの結果もnullになるため、
length
Int?として扱われます。

この値をさらに使用するときにも、
nullable型であることを考慮する必要があります。

letを使用してnullでない場合だけ処理する

Kotlinでは、
セーフコールと
letを組み合わせて、
nullでない場合だけ処理を実行できます。

val text: String? = "Hello"

text?.let {
    println(it.length)
}

textがnullでなければ
let内の処理が実行されます。

nullの場合は、
let内の処理は実行されません。

早期returnでnullを除外する

関数内でnullable型の値を使用する場合は、
nullであれば先に処理を終了する方法もあります。

fun showLength(text: String?) {

    if (text == null) {
        return
    }

    println(text.length)
}

nullの場合は先にreturnされるため、
その後の処理では
textをnullではない値として扱える場合があります。

Elvis演算子を利用して、
より短く記述することもできます。

fun showLength(text: String?) {

    val value = text ?: return

    println(value.length)
}

nullable型にする必要があるか確認する

エラーが発生した場合、
セーフコールを追加する前に、
そもそも対象の変数をnullable型にする必要があるか確認することも重要です。

val name: String? = "Taro"

この変数が必ず値を持ち、
nullになる可能性がないのであれば、
nullable型にする必要がない場合があります。

val name: String = "Taro"

println(name.length)

この場合は、
nameがnullになる可能性がないため、
通常の.でアクセスできます。

エラーを回避するためにすべての場所へ
?.を追加するのではなく、
その値が本当にnullableである必要があるかを確認することも重要です。

「?.」と「!!」の違い

エラーメッセージには
?.
!!.が表示されますが、
両者の動作は大きく異なります。

text?.length

?.では、
値がnullの場合に処理を実行せず、
結果としてnullを返します。

text!!.length

!!では、
値がnullではないものとして強制的に処理します。

実際にnullであった場合は、
実行時に例外が発生します。

値がnullになる可能性がある場合は、
?.やnullチェックを使用する方が安全です。
!!は、nullではないことを確実に保証できる場合に使用します。

「Only safe (?.) or non-null asserted (!!.) calls are allowed」が出たときの確認順序

原因が分からない場合は、
次の順番で確認すると切り分けやすくなります。

  1. エラーが表示されている変数の型を確認する
  2. 型の後ろに?が付いているか確認する
  3. その値が実際にnullになる可能性があるか確認する
  4. セーフコール?.で処理できるか確認する
  5. nullチェックを行ってからアクセスできるか確認する
  6. Elvis演算子?:でデフォルト値を指定できるか確認する
  7. letを使ってnullでない場合だけ処理できるか確認する
  8. 関数やAndroid APIの戻り値がnullable型か確認する
  9. そもそもnullable型にする必要があるか確認する
  10. !!で無理にエラーを回避していないか確認する

具体的なエラー箇所を確認する

「Only safe (?.) or non-null asserted (!!.) calls are allowed」
を解決するときは、
エラーが表示されているプロパティや関数だけでなく、
その前にあるオブジェクトがnullable型になっていないか確認します。

例:

val text: String? = null
println(text.length)
→ textがnullableなのでtext?.lengthを検討

val name = intent.getStringExtra("name")
println(name.length)
→ getStringExtra()の戻り値がnullになる可能性を確認

val name = users["user1"]
println(name.length)
→ Mapから取得した値がnullになる可能性を確認

val text: String? = "Hello"
println(text!!.length)
→ 本当にnullにならないことを保証できるか確認

まとめ

Kotlinの
「Only safe (?.) or non-null asserted (!!.) calls are allowed」は、
nullになる可能性があるnullable型の値に対して、
通常の方法でプロパティや関数へアクセスした場合に発生するエラーです。

Kotlinでは、
String
String?のように、
nullを許可しない型とnullable型が明確に区別されています。

エラーが表示された場合は、
まず対象となる変数や関数の戻り値が
nullable型になっているか確認します。

nullになる可能性がある場合は、
セーフコール演算子?.
nullチェック、
Elvis演算子?:
letなどを利用して、
nullを考慮した処理を記述します。

!!を使用すれば強制的にnon-nullとして扱えますが、
実際の値がnullの場合は実行時に例外が発生します。

単にコンパイルエラーを消すために
?.!!を追加するのではなく、
その値がなぜnullable型になっているのか、
本当にnullになる可能性があるのかを確認したうえで、
適切な処理方法を選択することが重要です。