Найти в Дзене
Kotlin King

Kotlin. Зачем нужен lateinit ?

Здравствуй, дорогой читатель!
👉 Сегодня мы поговорим о том зачем нужно lateinit в Kotlin.
Программируя на Kotlin, вы уже знаете, что есть переменные/свойства
какого либо типа, которые хранят значения,
например
var password : String = "paSsw0rD"
переменная password хранит, ваш пароль от умного тостера
или
var podarok : Box = Box()
или переменная podarok хранит объект "коробка" из которого можно извлечь подарок и что-то с ним сделать.
Так вот, в реальной ситуации не всегда бывает можно сразу иметь значения для некоторых переменных при создании класса.
Вы их просто не знаете еще, они еще не пришли по сети вам,
а может вы доверили создание своего класса библиотеке Hilt из Android, которая сама создаст их вместо вас позже .Или что-то еще, в общем значений сейчас у вас нет.
Но класс функционирующий (насколько это возможно) нужен-то сразу !
👉Допустим есть класс Upakovka, который содержит
коробку Box с подарком, то код мог бы выглядеть так:
class Box(){
fun openBox(){
Разберемся зачем нужен lateinit в Kotlin
Разберемся зачем нужен lateinit в Kotlin

Здравствуй, дорогой читатель!

👉 Сегодня мы поговорим о том зачем нужно lateinit в Kotlin.

Программируя на Kotlin, вы уже знаете, что есть переменные/свойства
какого либо типа, которые хранят значения,
например
var password : String = "paSsw0rD"
переменная
password хранит, ваш пароль от умного тостера
или
var podarok : Box = Box()
или переменная
podarok хранит объект "коробка" из которого можно извлечь подарок и что-то с ним сделать.

Так вот, в реальной ситуации не всегда бывает можно сразу иметь значения для некоторых переменных при создании класса.
Вы их просто не знаете еще, они еще не пришли по сети вам,
а может вы доверили создание своего класса библиотеке Hilt из Android, которая сама создаст их вместо вас позже .Или что-то еще, в общем значений сейчас у вас нет.

Но класс функционирующий (насколько это возможно) нужен-то сразу !
👉Допустим есть класс Upakovka, который содержит
коробку Box с подарком, то код мог бы выглядеть так:

class Box(){
fun openBox(){
//....
}


fun gift(){
//....
}
}

class Upakovka {
var podarok: Box
? = null
}


Тут мы видим что Box еще нет, потому там стоит null.

👉Но какие проблемы с таким подходом?
Тут стоит
восклицательный знак возле Box, что как бы говорит нам что упаковка может содержать null-подарок.
А мы не хотим null-подарок, мы хотим чтобы если что-то завернуто в упаковку, то оно там точно было.

Также, нам чтобы получить подарок из упаковки надо
писать знак вопроса каждый раз при доступе к подарку:

fun main() {
val upakovka = Upakovka()
upakovka.podarok
?.openBox()
upakovka.podarok
?.gift()
upakovka.podarok
?....

}

👉 Видите эти восклицательные знаки? Это все неудобно. У нас есть выбор либо оставить всё так, либо обойтись без null содержащих переменных вообще. Ведь мы же хотим чтобы подарок точно был не null. В этом нам поможет пометка для переменной lateinit!

Добавим ее сюда:

class Box(){
fun openBox(){
//....
}
fun gift(){
//....
}
}

class Upakovka {
lateinit var podarok: Box
}

fun main() {
val upakovka = Upakovka()
upakovka.podarok
.openBox()
upakovka.podarok
.gift()
}


👉 Видно, что мы тут пометили переменную podarok модификатором lateinit, что как бы является обещанием, что значение "будет, но позже".

Смысл в том, что вы как программист "говорите" Котлину, что значение точно проинициализируется, оно стопудняк будет. И после этого никогда не будет null, потому проверять на null его больше не надо нигде в коде.

Конечно, вы должны выполнить обещание: перед тем как обращаться к свойству podarok, вы должны проинициализировать его.

Иначе вы получите ошибку
UninitializedPropertyAccessException: lateinit property podarok has not been initialized

👉 Таким образом Котлин вас предупредит, что перед тем как дальше
работать с классом, вы должны были проинициализировать такую переменную. И это удобно, потому что, если что-то не так, то ошибка произойдет сразу в начале работы, когда это легко обнаружить и исправить, а не тогда, когда где-то в дебрях, в середине кода вы вдруг обнаружите что ваш код не выполняется, потому что там где-то затесался null.
Вот весь код примера:

class Box(){
fun openBox(){
//....
}
fun gift(){
//....
}
}

class Upakovka {
lateinit var podarok: Box
}

fun main() {
val upakovka = Upakovka()
upakovka.podarok = Box() //поэкспериментируйте с этой строкой самостоятельно
upakovka.podarok.openBox()
upakovka.podarok.gift()
}


👉 Также важно знать, что lateinit может быть использован в классах и в функциях (для обычных переменных).
Но вы не можете использовать lateinit с примитивными типами,
такими как Int, Double, Float, Boolean,... а можете только с объектами. Причем тип такого объекта не должен допускать null,
например можно
lateinit var podarok: Box
но нельзя
lateinit var podarok: Box?


Бонус:


👉 А как проверить что свойство podarok все-таки было инициализировано?

Это можно сделать изнутри класса где используется само lateinit, и называется эта проверка
.isInitialized

Давайте добавим ее в наш класс Upakovka:
fun isPodarokInitialised() = ::podarok.isInitialized

Вот так это выглядит

class Box(){
fun openBox(){
//....
}
fun gift(){
//....
}
}

class Upakovka {
lateinit var podarok: Box
fun isPodarokInitialised() = ::podarok.isInitialized

}

fun main() {
val upakovka = Upakovka()
upakovka.podarok = Box()

if (upakovka.isPodarokInitialised() ){
//поэкспериментируйте с этой строкой самостоятельно
println("подарок есть")
}

upakovka.podarok.openBox()
upakovka.podarok.gift()
}


👉 Поэкспериментируйте теперь lateinit с самостоятельно. А у меня на этом все. Напишите о чем бы вы еще хотели узнать?

Если вам понравилось поставьте лайк, напишите комментарий,
и
✅ подпишитесь на канал. Это действительно очень поможет.