できない.dev

Kotlin「lateinit property has not been initialized」の原因と直し方

lateinit property has not been initialized は、読む前に代入すれば直る。
代入が後になるなら、::name.isInitialized で確かめるか、by lazy やコンストラクタ引数に替える。

公開:

要約

lateinit property name has not been initialized は、lateinit(新しいタブで開く) のプロパティに値を入れる前に読むと投げられる例外である。読む前に代入すれば直る。lateinit はコンパイラの初期化チェックを外すので、コンパイルは通り、実行したときに初めて分かる。

class User {
    lateinit var name: String
    fun greet() = "Hello, $name"
}
 
fun main() { println(User().greet()) }
Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized
	at User.getName(A.kt:2)
	at User.greet(A.kt:3)
	at AKt.main(A.kt:5)

文言の name の位置には、プロパティの名前が入る。
スタックトレースの User.getName の次の行(ここでは User.greet)が、読んだ場所である。
kotlinc 1.9.25 と 2.4.21 で同じ文言になった。

解決策

1. 読む前に確かめる

代入されているかどうかは、::name.isInitialized で分かる。

class User {
    lateinit var name: String
    fun greet(): String {
        if (!::name.isInitialized) return "name is not set"
        return "Hello, $name"
    }
}

これを書けるのは、バッキングフィールドに触れる場所だけである。
クラスの外から user::name.isInitialized と書くと、backing field of 'var name: String' is not accessible at this point. でコンパイルが止まる。
外から知りたいなら、クラスの中に判定用のメソッドを用意する。

fun isReady() = ::name.isInitialized

2. コンストラクタ引数に替える

インスタンスを作るときに値が決まるなら、lateinit は要らない。

class Repo(val url: String)

3. by lazy に替える

初めて読むときに値を作れるなら、by lazy にする。
初回に一度だけブロックが走り、2 回目はその値を返す。val にできるので、あとから書き換えられない。

class Config {
    val conf: String by lazy { "value" }
}

4. 値が無い状態も扱うなら null を許す

未設定が正常な状態としてありうるなら、String? にする。null の扱いを書かないとコンパイルエラーになるので、実行時の例外にはならない。

class Repo {
    var url: String? = null
    fun show() = url ?: "unset"
}

lateinit を書けない型

lateinit は、var で、null を許さない、プリミティブでない型にしか付けられない。
これらに付けるとコンパイルエラーになり、例外まで進まない。

書いたものkotlinc 2.4.21 のエラー
lateinit var age: Int'lateinit' modifier is not allowed on properties of primitive types.
lateinit var name: String?'lateinit' modifier is not allowed on properties of a type with nullable upper bound.
lateinit val name: String'lateinit' modifier is allowed only on mutable properties.

kotlinc 1.9.25 では、2 行目の文言が not allowed on properties of nullable types だった。

よくある原因

  1. 代入するメソッドを呼ぶ前に読んでいる。setup() のようなメソッドで代入する設計だと、呼び忘れや呼ぶ順番の入れ替えがそのまま例外になる。
  2. init ブロックや親クラスのコンストラクタの中で読んでいる。
    親のコンストラクタから open メソッドを呼ぶと、子の代入より先に読むことになる。
    このときスタックトレースには Base.<init> のような行が出る。
  3. 別のインスタンスのプロパティを読んでいる。a で connect() を呼んだあとに、新しく作った b の値を読むと、b のプロパティは空のままである。