Lateinit kotlin что это

от admin

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

Ключевое слово 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 var, val, lateinit, lazy, getters & setters

Kotlin is a very concise language with handy mutability and immutability features. Let’s see how variable declaration and initialization works in kotlin with those features.

var, Kotlin’s keyword representing mutable, non-final variables. Once initialized, we’re free to mutate/change the data held by the variable.

Let’s take a look at how this works:

var myVariable = 1

Behind the scenes, myVariable initializes with the Int data type.

Although Kotlin uses type inference, which means we also have the option to specify the data type when we initialize the variable like below:

var myVariable: Int = 1

Variables declared as one data type and then initialized with a value of the wrong type will result in an error.

var myVariable: Int = b //ERROR! as b is different type

val

val keyword works same as the var keyword, but with one key difference the variable is read-only/nonmutable. The use of val is like declaring a new variable in Java with the final keyword.

For example, in Kotlin:

val name: String = "Kotlin"

Whereas in Java:

final String name = "Kotlin";

val variables must be assigned at declaration, or in a Classconstructor.

class Address(val street: String) <

val name: String = "Kotlin"

>

lateinit

lateinit means late initialization. If you do not want to initialize a variable in the constructor instead you want to initialize it later on and if you can guarantee the initialization before using it, then declare that variable with lateinit keyword. It will not allocate memory until initialized.

Then initialize somewhere in your MyViewModel class

Y ou cannot use val for lateinit variable as it will be initialized later on. Also you must guarantee that you are initializing the variable before using the variable otherwise it will throw exception at runtime. You cannot use lateinit for primitive type properties like Int, Long etc.

Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property being accessed and the fact that it hasn't been initialized.

It means lazy initialization. Your variable will not be initialized unless you use that variable in your code. It will be initialized only once after that we always use the same value.

lazy() is a function that takes a lambda and returns an instance of lazy which can serve as a delegate for implementing a lazy property: the first call to get() executes the lambda passed to lazy() and remembers the result, subsequent calls to get() simply return the remembered result.

lateinit or lazy

If variable are mutable (i.e. might change at a later stage) use lateinit

lateinit var can be initialized from anywhere the object is seen from.

lazy can only be used for val properties, whereas lateinit can only be applied to var because it can’t be compiled to a final field, thus no immutability can be guaranteed.

If its only meant to initialized once and shared by all, and it’s more internally set (dependent on variable internal to the class), then use lazy.

getter

getters are used for getting value of the property. Kotlin internally generates a getter for read-only properties using val. The getter is optional in kotlin. Property type is optional if it can be inferred from the initializer.

If we define a custom getter, it will be called every time we access the property like below:

setter

setters are used for setting value of the property. Kotlin internally generates a default getter and setter for mutable properties using var. Property type is optional if it can be inferred from the initializer.

If we define a custom setter, it will be called every time we assign a value to the property. A custom setter looks like this:

By convention, the name of the setter parameter is value, but you can choose a different name if you prefer.

You can make setter as private so that outsider can’t set the value.

Свойства

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

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

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

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

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

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

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

Вы можете опустить тип свойства, если его можно определить с помощью геттера.

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

Читать:
Как зарегистрировать домен на чужое имя

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

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

Теневые поля

В Kotlin поле используется только как часть свойства для хранения его значения в памяти. Поля не могут быть объявлены напрямую. Однако, когда свойству требуется теневое поле (backing field), Kotlin предоставляет его автоматически. На это теневое поле можно обратиться в методах доступа, используя идентификатор field :

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

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

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

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

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

On the JVM: Access to private properties with default getters and setters is optimized to avoid function call overhead. —>

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

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

Если значение константного (read-only) свойства известно во время компиляции, пометьте его как константы времени компиляции, используя модификатор const . Такие свойства должны соответствовать следующим требованиям:

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

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

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

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

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

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

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

Проверка инициализации lateinit var

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

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

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

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

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

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

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

Разработчики 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<T> . Данный объект реализует ленивое вычисление значения свойства: при первом обращении к свойству метод get() запускает лямбда-выражение (переданное lazy() в качестве аргумента) и запоминает полученное значение, а последующие вызовы просто возвращают запомненное значение.

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

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

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

Похожие статьи