Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Отложенная инициализация(by lazy, lateinit)

by lazy
lateinit

by lazy

Ключевое слово by lazy служит для отложенной инициализации через механизм делегатов. Делегат lazy принимает лямбда-выражение с кодом, который вы бы хотели выполнить для инициализации свойства.

Иногда требуется объявить переменную, но отложить инициализацию. Причины задержки инициализации могут быть самыми разными. Например, для инициализации требуются предварительные вычисления, которые желательно не выполнять, если их результат никогда не будет использован. Обычно в таких случаях переменную объявляют через var, которая получает значение null, пока не будет инициализирована нужным значением, которое потом никогда не меняется.


var catName: String? = null
...
catName = getName() // присваиваем значение

Это неудобно, если вы знаете, что у переменной будет конкретное значение и вы хотите избегать значения null. В нашем примере переменная имеет тип String?, который поддерживает значение null, хотя могла бы иметь тип String. Как вариант, можно было использовать заданное значение, например:


var catName: String = "NOT_INITIALIZED"
// или пустая строка
var catName: String = ""
...
catName = getValue()

В любом случае приходится использовать var, даже зная, что после инициализации значение переменной никогда не изменится. Kotlin предлагает для подобных случаев использовать by lazy:


val catName: String by lazy { getName() }

В этом случае функция getName() будет вызвана только один раз, при первом обращении к catName. Вместо лямбда-выражения можно также использовать ссылку на функцию:


val catName: String by lazy(::getName)

Ещё пример.


val infoTextView by lazy { view!!.findViewById<TextView>(R.id.textView) }

Модификатор lateinit

Иногда переменную нельзя сразу инициализировать, сделать это можно чуть позже. Для таких случаев придумали новый модификатор lateinit (отложенная инициализация). Это относится только к изменяемым переменным.


private lateinit var button: Button

Переменная обязательно должна быть изменяемой (var). Не должна относиться к примитивным типам (Int, Double, Float и т.д). Не должна иметь собственных геттеров/сеттеров.

Подобный подход удобен во многих случаях, избегая проверки на null. В противном случае пришлось бы постоянно использовать проверку или утверждение !!, что засоряет код.

Если вы обратитесь к переменной до её инициализации, то получите исключение "lateinit property ... hos not been initialized" вместо NullPointerException.

В любое объявление var-свойства можно добавить ключевое слово lateinit. Тогда Kotlin позволит отложить инициализацию свойства до того момента, когда такая возможность появится. Это полезная возможность, но её следует применять осторожно. Если переменная с поздней инициализацией получит начальное значение до первого обращения к ней, проблем не будет. Но если сослаться на такое свойство до его инициализации, то получим исключение UninitializedPropertyAccessException. Как вариант, можно использовать тип с поддержкой null, но тогда придётся обрабатывать возможное значение null по всему коду. Получив начальное значение, переменные с поздней инициализацией будут работать так же, как другие переменные.

Для проверки факта инициализации переменной вызывайте метод isInitialized(). Функцию следует использовать экономно — не следует добавлять эту проверку к каждой переменной с поздней инициализацией. Если вы используете isInitialized() слишком часто, то скорее всего вам лучше использовать тип с поддержкой null.


lateinit var catName: String

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)
 
    catName = "Barsik"

    if (::catName.isInitialized) {
        Log.d("Kot", "Hi, $catName")
    }
}
Реклама