Глиф Учебники

lateinit — отложенная инициализация

Справочник · Kotlin · 1 минута чтения

Свойство класса в Kotlin должно получить значение сразу — в конструкторе или при объявлении. Иначе компилятор сообщит, что свойство должно быть инициализировано (Property must be initialized), то есть получить первое значение. Но бывает, что значение появится позже: после загрузки данных, в методе настройки, в onCreate экрана Android. Для таких случаев есть lateinit:

Kotlin
class Profile {
    lateinit var name: String

    fun load() {
        name = "Anna"
    }

    fun show() {
        if (::name.isInitialized) {
            println("Name: $name")
        } else {
            println("not loaded")
        }
    }
}

fun main() {
    val p = Profile()
    p.show()
    p.load()
    p.show()
}
Вывод
not loaded
Name: Anna

lateinit var name: String — обещание компилятору: «значение будет присвоено до первого чтения». Тип остаётся обычным, без вопроса, и проверять на null не нужно.

::name.isInitialized проверяет, присвоено ли значение. Такая проверка работает внутри класса, где объявлено свойство.

Если прочитать раньше времени

Текст
kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized

Программа обратилась к свойству до того, как ему присвоили значение. Найдите в стеке вызовов строку своего файла (строки at … под сообщением) и проверьте порядок: присваивание должно выполниться раньше. В Android так бывает, если свойство, которое заполняется в onCreate, прочитать раньше, чем onCreate выполнился.

Ограничения

Чем заменить

Что взять вместо lateinit, зависит от случая — выберите свой.

Значения может не бытьтип со знаком ?

Подходит, если «значения нет» — нормальное состояние, а не ошибка:

Kotlin
class Session {
    var user: String? = null
}

Компилятор заставит проверять null при каждом обращении, зато программа не упадёт.

Значение можно посчитать при первом чтенииby lazy

by lazy — для val, значение которого считается при первом обращении:

Kotlin
class Report {
    val text: String by lazy {
        println("building report")
        "Report ready"
    }
}

fun main() {
    val r = Report()
    println("created")
    println(r.text)
    println(r.text)
}
Вывод
created
building report
Report ready
Report ready

Блок в lazy { } выполнился один раз — при первом чтении r.text; второй раз значение взято готовым.

Все три способа рядом:

lateinit varby lazyString? = null
Изменяемоеданет, это valда
Кто присваиваеткод снаружи или метод классасам блок lazyкто угодно
Простые типы (Int)нельзяможноможно
Если значения ещё нетошибка при чтениипосчитаетсяnull