lateinit var kotlin что это

Грабли, спрятанные в Kotlin

Kotlin создавался, чтобы избежать некоторых проблем Java. Но как и в любом языке, есть в нем свои особенности. Разрабатывая собственный проект, мы наткнулись на несколько таких моментов. Часть стреляет вам в колено на продакшене, только если вы ими злоупотребляете. Другая отражается на производительности высоконагруженных систем. Все эти моменты сложно заметить, поскольку их не подсвечивают специально плагины для IDE, да и в целом на первый взгляд код похож на валидный.

В этой статье мы поговорим о том, на что нужно обратить внимание.

lateinit var kotlin что это

Не злоупотребляйте обходом null-безопасности

Lateinit

Используя в коде переменную non-null типа, мы должны сразу присвоить ей значение, иначе код не скомпилируется. Но бывают ситуации, когда инициализировать изменяемую non-null переменную нужно не сразу. Например, с таким можно столкнуться в Spring.

Как раз на такой случай в Kotlin предусмотрен модификатор lateinit. Он откладывает инициализацию переменной на потом.

lateinit var kotlin что этоФактически, подписывая lateinit, мы говорим компилятору, что позаботимся об этом позже (и не будем здесь и сейчас писать лишние проверки на null) Здесь и далее мы приводим код скриншотами, чтобы было видно, как IDEA подсвечивает синтаксис.

Кстати, lateinit в принципе нельзя использовать с примитивными типами (подробные разъяснения можно найти здесь).

Однако если переменная все-таки окажется null, NullPointerException не избежать, правда, уже не на этапе компиляции, а в рантайме.

lateinit var kotlin что этоNullPointerException

Аккуратнее с функциями расширения

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

В отличие от Java Kotlin умеет расширять классы через extension-функции. Поскольку эти функции связаны с конкретным классом, поведение может меняться как раз в зависимости от используемого класса. И за этим сложно уследить.

Предположим, мы создаем MutableMap, инициализируем ее через ConcurrentHashMap и вызываем getOrPut:

lateinit var kotlin что этоСоздаем MutableMap, инициализируем ее через ConcurrentHashMap и вызываем getOrPut

Код выглядит нормально, но работать он не будет.

lateinit var kotlin что этоПосмотрим, что под капотом.

Чтобы код заработал, необходимо прописать явно ConcurrentHashMap:

lateinit var kotlin что этоПрописываем ConcurrentHashMap явно

Тогда будет использоваться потокобезопасная extension-функция, т.е. все будет работать корректно.

lateinit var kotlin что этоПод капотом.

А вот еще одна похожая ситуация, тоже по мотивам одного из модулей нашего проекта.

В коде мы проверяем наличие “3” и “8” во множестве строк hashSet.

lateinit var kotlin что этоДва способа проверки присутствия 3 и 8 в HashSet

На скриншоте два варианта кода, реализующего нужный нам функционал. В первом случае мы хардкодим строки, которые ищем во множестве. Во втором (после комментария) передаем в метод contains поля переменной numbers.

Все это не значит, что нужно отказываться от extension-функций, но стоит внимательно смотреть на то, что происходит внутри. Не зря IDEA подсвечивает extension-функции цветом.

Завершая тему extension-функций, хотим поделиться еще одним забавным примером.

lateinit var kotlin что этоtoString() для null объекта выдает строку «null» в 4 символа

Ищите StringBuilder даже там, где его нет

Для построения строк в Kotlin используется специальный класс StringBuilder (он пришел еще из Java). В ряде ситуаций он помогает быстрее строить строки, не создавая множество промежуточных новых объектов. Но иногда StringBuilder влезает там, где не просят. Вот еще один пример с нашего реального проекта (как и в прошлых примерах, код мы переписали специально под статью).

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

lateinit var kotlin что этоКазалось бы, мы складываем две строки через конкатенацию

Кажется, что Kotlin складывает две строки через конкатенацию, но на самом деле во многих случаях Kotlin использует StringBuilder. И он тормозит работу, если речь идет о высоконагруженном сервисе.

Kotlin инициализирует StringBuilder с capacity, определяемым некой константой. Если этой capacity не хватает, начинается выделение памяти. И в нашем случае метод выделения новой памяти как раз и тормозил весь сервис. При этом в коде в явном виде StringBuilder-а не было.

На втором скриншоте показан тот же код, но декомпилированный на Java.

lateinit var kotlin что этоЧто под капотом.

Здесь выделено место, где создаётся новый StringBuilder с capacity по умолчанию 16 символов. Поскольку строка «Current timestamp is» не влезает в эти 16 символов, память выделяется повторно. Получается двойная работа, которая хорошо видна под нагрузкой.

Проверяйте дважды после обновления версии языка

Но на продакшене мы отключаем логгер, чтобы производительность не деградировала и жесткий диск не переполнялся (в trace сервис пишет сотни Гб в сутки). Для этого мы по сути добавляли класс, как тот, что выделен на скриншоте, в черный список логирования.

lateinit var kotlin что этоКак мы ограничиваем логирование

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

lateinit var kotlin что этоКак это выглядит после обновления версии языка.

Фактически, мы были в шаге от аварии.

И до сих пор в Kotlin модификатор доступа private влияет на то, как класс называется в логе.

Мы уверены, что наткнулись далеко не на все особенности языка. Самое интересное еще впереди! А приходилось ли вам сталкиваться с чем-то подобным?

Источник

Properties

Declaring properties

Properties in Kotlin classes can be declared either as mutable, using the var keyword, or as read-only, using the val keyword.

To use a property, simply refer to it by its name:

Getters and setters

The full syntax for declaring a property is as follows:

The initializer, getter, and setter are optional. The property type is optional if it can be inferred from the initializer or from the initializer’s or the getter’s return type, as shown below:

The full syntax of a read-only property declaration differs from a mutable one in two ways: it starts with val instead of var and does not allow a setter:

You can define custom accessors for a property. If you define a custom getter, it will be called every time you access the property (this way you can implement a computed property). Here’s an example of a custom getter:

You can omit the property type if it can be inferred from the getter:

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

If you need to annotate an accessor or change its visibility, but you don’t need to change the default implementation, you can define the accessor without defining its body:

Backing fields

In Kotlin, a field is only used as a part of a property to hold its value in memory. Fields cannot be declared directly. However, when a property needs a backing field, Kotlin provides it automatically. This backing field can be referenced in the accessors using the field identifier:

The field identifier can only be used in the accessors of the property.

A backing field will be generated for a property if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.

For example, there would be no backing field in the following case:

Backing properties

If you want to do something that does not fit into this implicit backing field scheme, you can always fall back to having a backing property:

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

Compile-time constants

If the value of a read-only property is known at compile time, mark it as a compile time constant using the const modifier. Such a property needs to fulfil the following requirements:

It must be a top-level property, or a member of an object declaration or a companion object.

It must be initialized with a value of type String or a primitive type

It cannot be a custom getter

Such properties can be used in annotations:

Late-initialized properties and variables

Normally, properties declared as having a non-null type must be initialized in the constructor. However, it is often the case that doing so is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In these cases, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

To handle such cases, you can mark the property with the lateinit modifier:

This modifier can be used on var properties declared inside the body of a class (not in the primary constructor, and only when the property does not have a custom getter or setter), as well as for top-level properties and local variables. The type of the property or variable must be non-null, and it must not be a primitive type.

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.

Checking whether a lateinit var is initialized

This check is only available for properties that are lexically accessible when declared in the same type, in one of the outer types, or at top level in the same file.

Overriding properties

Delegated properties

The most common kind of property simply reads from (and maybe writes to) a backing field, but custom getters and setters allow you to use properties so one can implement any sort of behavior of a property. Somewhere in between the simplicity of the first kind and variety of the second, there are common patterns for what properties can do. A few examples: lazy values, reading from a map by a given key, accessing a database, notifying a listener on access.

Such common behaviors can be implemented as libraries using delegated properties.

Источник

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

lateinit

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

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

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

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

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

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

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

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

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

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

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

Источник

Kotlin var, val, lateinit, lazy, getters & setters

Apr 15, 2019 · 3 min read

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:

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 k e yword 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»

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.

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.

Feel free to share your thoughts or feedbacks. Happy Kotlin…

Источник

Ленивая инициализация в Котлине

1. Обзор

Мы также рассмотрим ключевое слово lateinit, которое позволяет нам обмануть компилятор и инициализировать ненулевые поля в теле класса, а не в конструкторе.

2. Шаблон отложенной инициализации в Java

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

В качестве альтернативы мы можем использовать статическую инициализацию внутреннего объекта в Java для достижения лени:

Мы можем проверить, что getInstance () возвращает один и тот же экземпляр каждый раз, когда он вызывается:

3. Ленивая инициализация в Котлине

Чтобы создать объект, который будет инициализирован при первом обращении к нему, мы можем использовать ленивый метод:

Как мы видим, лямбда, переданная ленивой функции, была выполнена только один раз.

Мы можем передать ПУБЛИКАЦИЮ как режим, что приведет к тому, что каждый поток может инициализировать данное свойство. Объект, присвоенный ссылке, будет первым возвращенным значением, поэтому первый поток выигрывает.

Давайте посмотрим на этот сценарий:

Мы видим, что запуск двух потоков одновременно приводит к тому, что инициализация ClassWithHeavyInitialization происходит дважды.

4. Задержка Котлина

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

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

Если мы забыли инициализировать lateinit свойства, мы получим UninitializedPropertyAccessException :

Стоит упомянуть, что мы можем использовать переменные lateinit только с непримитивными типами данных. Следовательно, невозможно написать что-то подобное:

И если мы это сделаем, то получим ошибку компиляции:

5. Заключение

В этом кратком руководстве мы рассмотрели отложенную инициализацию объектов.

Во-первых, мы увидели, как создать потокобезопасную отложенную инициализацию в Java. Мы увидели, что это очень громоздко и требует большого количества шаблонного кода.

Реализацию всех этих примеров и фрагментов кода можно найти на GitHub.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *