lateinit — отложенная инициализация
Свойство класса в Kotlin должно получить значение сразу — в конструкторе или при объявлении. Иначе компилятор сообщит, что свойство должно быть инициализировано (Property must be initialized), то есть получить первое значение. Но бывает, что значение появится позже: после загрузки данных, в методе настройки, в onCreate экрана Android. Для таких случаев есть lateinit:
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: Annalateinit var name: String — обещание компилятору: «значение будет присвоено до первого чтения». Тип остаётся обычным, без вопроса, и проверять на null не нужно.
::name.isInitialized проверяет, присвоено ли значение. Такая проверка работает внутри класса, где объявлено свойство.
Если прочитать раньше времени
kotlin.UninitializedPropertyAccessException: lateinit property name has not been initializedПрограмма обратилась к свойству до того, как ему присвоили значение. Найдите в стеке вызовов строку своего файла (строки at … под сообщением) и проверьте порядок: присваивание должно выполниться раньше. В Android так бывает, если свойство, которое заполняется в onCreate, прочитать раньше, чем onCreate выполнился.
Ограничения
- только
var, неval; - тип не может быть со знаком
?; - нельзя для
Int,Double,Boolean,Charи других простых типов — только для классов вродеString,Listили своих; - у свойства не должно быть своих
get()иset(); - нельзя в основном конструкторе.
Чем заменить
Что взять вместо lateinit, зависит от случая — выберите свой.
Значения может не бытьтип со знаком ?
Подходит, если «значения нет» — нормальное состояние, а не ошибка:
class Session {
var user: String? = null
}Компилятор заставит проверять null при каждом обращении, зато программа не упадёт.
Значение можно посчитать при первом чтенииby lazy
by lazy — для val, значение которого считается при первом обращении:
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 var | by lazy | String? = null | |
|---|---|---|---|
| Изменяемое | да | нет, это val | да |
| Кто присваивает | код снаружи или метод класса | сам блок lazy | кто угодно |
Простые типы (Int) | нельзя | можно | можно |
| Если значения ещё нет | ошибка при чтении | посчитается | null |