Android kotlin lateinit check is initialized

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

by lazy

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Kotlin. Отложенная и ленивая инициализация свойств

lateinit

Разработчики Kotlin крайне серьёзно относятся к проверкам на null. Поэтому, как правило, свойства, которые по логике программы должны хранить ненулевые значения инициализируются в конструкторе.

Тем не менее бывают ситуации, когда такой подход не особо удобен. Например, если вы хотите инициализировать свойства через внедрение зависимостей. Kotlin предусматривает такую возможность и предлагает использовать отложенную (позднюю) инициализацию. Осуществляется это с помощью модификатора lateinit.

Модификатор lateinit говорит о том, что данная переменная будет инициализирована позже. При этом инициализировать свойство можно из любого места, откуда она видна.

Правила использования модификатора lateinit:

  • используется только совместно с ключевым словом var;
  • свойство может быть объявлено только внутри тела класса (не в основном конструкторе);
  • тип свойства не может быть нулевым и примитивным;
  • у свойства не должно быть пользовательских геттеров и сеттеров;
  • с версии Kotlin 1.2 можно применять к свойствам верхнего уровня и локальным переменным.
Читайте также:  Тренировка памяти для андроид

Если обратиться к свойству с модификатором lateinit до того, как оно будет проинициализировано, то получите ошибку, которая явно указывает, что свойство не было определено:

В версии Kotlin 1.2 модификатор был улучшен. Теперь перед обращением к переменной можно проверить была ли она инициализирована. Осуществляется это с помощью метода .isInitialized . Данная функция вернет true, если переменная инициализирована и false, если нет.

Когда стоит использовать?

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

По факту lateinit появился с целью облегчить инъекцию зависимостей через Dagger. До его появления приходилось свойства, которые будут инъектиться, объявлять как nullable — ведь мы не можем такому свойству задать какое-либо значение кроме null . Это приводило к тому, что все вызовы этого свойства должны были сопровождаться проверкой на null . Так и появился lateinit .

Соответственно из этого можно сделать вывод: по возможности избегайте использования lateinit . По факту только в одном случае никак не избежать его использования — при инъекции зависимостей. В остальных случаях постарайтесь найти другой выход, например, используйте “ленивую” инициализацию (о ней ниже) или инициализируйте поле с начальным значением null . В этом случае по крайней мере вам компилятор будет подсказывать о необходимости проверки на null .

Но почему стоит избегать? В основном из-за того, что lateinit часто используют неправильно.

Пример из андроида: у вас во фрагменте есть lateinit -переменная, которая инициализируется в onCreateView . А теперь по шагам:

  • Фрагмент создался.
  • Создалась view для фрагмента. В lateinit -переменную было сохранено значение из view .
  • Фрагмент ушел в backstack (например, был заменён на другой фрагмент). Вызывается метод onDestroyView (но не onDestroy ), который уничтожит view , но не ссылку на него в lateinit -переменной.
  • При возвращении к фрагменту в lateinit -переменную присвоится новая ссылка на view , тогда как старый объект будет ещё какое-то время висеть в памяти.

Если эти шаги повторить, скажем, 10 раз подряд, то у вас в памяти будет висеть уже 10 бесполезных объектов, которые уничтожатся только с уничтожением самого фрагмента.

Поэтому используйте lateinit с осторожностью, чтобы потом не удивляться от возникновения неожиданных последствий.

Для более подробной информации рекомендую ознакомиться с видео — lateinit — это зло и «костыль» Kotlin. Dagger 2 всему виной.

Помимо отложенной инициализации в Kotlin существует ленивая инициализация свойств. Такая инициализация осуществляется с помощью функции lazy() , которая принимает лямбду, а возвращает экземпляр класса Lazy . Данный объект реализует ленивое вычисление значения свойства: при первом обращении к свойству метод get() запускает лямбда-выражение (переданное lazy() в качестве аргумента) и запоминает полученное значение, а последующие вызовы просто возвращают запомненное значение.

Ленивая инициализация может быть использована только совместно с ключевым словом val.

Свойство, инициализированное подобным образом, называется делегированным свойством. Потому что мы делегировали вычисление значения классу-делегату Lazy . Данный класс является частью стандартной библиотеки Kotlin и именно в нем реализован get-метод вычисляющий и возвращающий значение.

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

Источник

Safely accessing lateinit properties in Kotlin

Kotlin, by design, doesn’t allow a non-null variable to be left uninitialized during it’s declaration. Whenever you declare a lateinit var, you need to initialize it before you access it. Otherwise, you’ll be greeted with a fancy exception like this:

Don’t mistake this for a puny exception. It’ll crash your app. So, how to solve this problem?

Taking the rookie approach

The most lucrative solution to this problem would be to make the property a regular nullable one instead of a lateinit var and assign a value later on. You can do something like this:

And then just do a plain null check or kotlin null-check operator ? whenever you’re accessing the value.

Kind of like Java. But hang on a sec, Kotlin is supposed to be better than Java. Also, one of the USPs of Kotlin was eliminating the fiasco caused by a NullPointerException .

Читайте также:  Где андроид хранит удаленные файлы

So, why go the traditional route?

Here’s a better solution.

Going the Kotlinish way

If you’re using Kotlin 1.2, you can easily check whether a lateinit variable has been initialized or not. If not, well, you can always use the not null approach.

Anyways, here’s how you can check if a lateinit var has been initialized or not:

7 years experience. 💻 Creator of various Open Source libraries on Android . 📝 Author of two technical books and 100+ articles on Android. 🎤 A passionate Public Speaker giving talks all over the world.

Источник

Safely accessing lateinit properties in Kotlin

Kotlin, by design, doesn’t allow a non-null variable to be left uninitialized during its declaration.

If you’ve been digging into Kotlin you’ll know that a lateinit property allows you to overcome this hurdle.

However, if you’re a complete newbie, lateinit allows you to declare a variable first and then initialize is some point in the future during your app’s execution cycle.

All these seem rosy, so where’s the problem?

The problem occurs when you try to access an uninitialized property. Let’s talk more about that.

Accessing an uninitialized property

Whenever you declare a lateinit var , you need to initialize it before you access it. Otherwise, you’ll be greeted with a fancy exception like this:

Don’t mistake this for a puny exception. It’ll crash your app.

And this is a very common situation to run into. Take this as an example:

You might have a list data being fetched from a remote server and need to initialize a RecyclerView adapter based on that data.

What happens when you try to access that adapter before you actually get the data from the remote server?

Boom! Your app crashes.

So, how to solve this problem?

Taking the rookie approach

The most lucrative solution to this problem would be to make the property a regular nullable one instead of a lateinit var and assign a value later on.

You can do something like this:

And then just do a plain null check whenever you’re accessing the value.

Kind of like Java. But hang on a sec, Kotlin is supposed to be better than Java. Also, one of the USPs of Kotlin was eliminating the fiasco caused by a NullPointerException .

So, why go the traditional route?

Here’s a better solution.

Going the Kotlinish way

If you’re using Kotlin 1.2, you can easily check whether a lateinit variable has been initialized or not. If not, well, you can always use the not null approach.

Anyways, here’s how you can check if a lateinit var has been initialized or not:

According to the official blog post announcing this update, this approach uses reflection to check whether the value has been initialized or not.

Also, a deinitialize method is due for rollout in a future release, probably in Kotlin 1.3.

What’s the motivation here?

When I first encountered the exception mentioned in this article, I had two choices:

  • Go the traditional not null way
  • Do some cool shit

I took the latter approach.

I didn’t want to litter my code with null checks. Also, this looks more meaningful.

Traditional is, well, traditional. Go along with the new.

Источник

Свойства и поля

Объявление свойств

Классы в Kotlin могут иметь свойства: изменяемые (mutable) и неизменяемые (read-only) — var и val соответственно.

Для того, чтобы воспользоваться свойством, мы просто обращаемся к его имени (как в Java):

Геттеры и сеттеры

Полный синтаксис объявления свойства выглядит так:

Инициализатор property_initializer, геттер и сеттер можно не указывать. Также необязательно указывать тип свойства, если он может быть выведен из контекста или наследован от базового класса.

Синтаксис объявления констант имеет два отличия от синтаксиса объявления изменяемых переменных: во-первых, объявление константы начинается с ключевого слова val вместо var , а во-вторых, объявление сеттера запрещено:

Читайте также:  Самый дорогой андроид смартфон 2021 года

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

Пользовательский сеттер выглядит примерно так:

По договорённости, имя параметра сеттера — value , но вы можете использовать любое другое.

Если вам нужно изменить область видимости метода доступа или пометить его аннотацией, при этом не внося изменения в реализацию по умолчанию, вы можете объявить метод доступа без объявления его тела:

Теневые поля

Классы в Kotlin не могут иметь полей. Т.е. переменные, которые вы объявляете внутри класса только выглядят и ведут себя как поля из Java, хотя на самом деле являются свойствами, т.к. для них неявно реализуются методы get и set. А сама переменная, в которой находится значение свойства, называется теневое поле (backing field). Однако, иногда, при использовании пользовательских методов доступа, необходимо иметь доступ к теневому полю. Для этих целей Kotlin предоставляет автоматическое теневое поле, к которому можно обратиться с помощью идентификатора field :

Идентификатор field может быть использован только в методах доступа к свойству.

Теневое поле будет сгенерировано для свойства, если оно использует стандартную реализацию как минимум одного из методов доступа, либо если пользовательский метод доступа ссылается на него через идентификатор field .

Например, в нижестоящем примере не будет никакого теневого поля:

Теневые свойства

Если вы хотите предпринять что-то такое, что выходит за рамки вышеуказанной схемы «неявного теневого поля«, вы всегда можете использовать теневое свойство (backing property):

Такой подход ничем не отличается от подхода в Java, так как доступ к приватным свойствам со стандартными геттерами и сеттерами оптимизируется таким образом, что вызов функции не происходит.

Константы времени компиляции

Свойства, значения которых известны во время компиляции, могут быть помечены как константы времени компиляции. Для этого используется модификатор const . Такие свойства должны соответствовать следующим требованиям:

  • Находиться на самом высоком уровне или быть членами объекта object
  • Быть проинициализированными значением типа String или значением примитивного типа
  • Не иметь переопределённого геттера

Такие свойства могут быть использованы в аннотациях:

Свойства с поздней инициализацией

Обычно, свойства, объявленные non-null типом, должны быть проинициализированы в конструкторе. Однако, довольно часто это неосуществимо. К примеру, свойства могут быть инициализированы через внедрение зависимостей, в установочном методе (ориг.: «setup method») юнит-теста или в методе onCreate в Android. В таком случае вы не можете обеспечить non-null инициализацию в конструкторе, но всё равно хотите избежать проверок на null при обращении внутри тела класса к такому свойству.

Для того, чтобы справиться с такой задачей, вы можете пометить свойство модификатором lateinit :

Такой модификатор может быть использован только с var свойствами, объявленными внутри тела класса (не в основном конструкторе, и только тогда, когда свойство не имеет пользовательских геттеров и сеттеров) и, начиная с Kotlin 1.2, со свойствами, расположенными на верхнем уровне, и локальными переменными. Тип такого свойства должен быть non-null и не должен быть примитивным.

Доступ к lateinit свойству до того, как оно проинициализировано, выбрасывает специальное исключение, которое чётко обозначает, что свойство не было определено.

Проверка инициализации lateinit var (начиная с версии 1.2)

Чтобы проверить, было ли проинициализировано lateinit var свойство, используйте .isInitialized метод ссылки на это свойство:

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

Переопределение свойств

Делегированные свойства

Самый распространённый тип свойств просто считывает (или записывает) данные из теневого поля. Тем не менее, с пользовательскими геттерами и сеттерами мы можем реализовать совершенно любое поведение свойства. В реальности, существуют общепринятые шаблоны того, как могут работать свойства. Несколько примеров:

  • Вычисление значения свойства при первом доступе к нему (ленивые свойства)
  • Чтение из ассоциативного списка с помощью заданного ключа
  • Доступ к базе данных
  • Оповещение listener’а в момент доступа и т.п.

Такие распространённые поведения свойств могут быть реализованы в виде библиотек с помощью делегированных свойств.

Источник

Оцените статью