Kotlin - Инициализация имущества с использованием "ленивым" и "lateinit"

В Kotlin, если вы не хотите инициализировать свойство класса внутри конструктора или в верхней части тела класса, у вас есть в основном эти две опции (из ссылки на язык):

  1. Ленивая инициализация

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

пример

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

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

  1. Поздняя инициализация

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

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

public class MyTest {

   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

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

Итак, как правильно выбрать один из этих двух вариантов, поскольку оба они могут решить одну и ту же проблему?

Ответ 1

Вот значительные различия между lateinit var и by lazy {... } делегированным свойством:

  • lazy {... } делегат может использоваться только для свойств val, тогда как lateinit может применяться только к var s, потому что он не может быть скомпилирован в final поле, поэтому неизменность не может быть гарантирована;

  • lateinit var имеет поле поддержки, в котором хранится значение, и by lazy {... } создает объект делегата, в котором значение сохраняется после вычисления, сохраняет ссылку на экземпляр делегата в объекте класса и генерирует метод получения свойства это работает с экземпляром делегата. Поэтому, если вам нужно поле поддержки, присутствующее в классе, используйте lateinit;

  • В дополнении к val с, lateinit не может быть использован для обнуляемых свойств и Java примитивных типов (это из - за null, используемым для неинициализированного значения);

  • lateinit var может быть инициализирован из любого места, откуда виден объект, например, из кода структуры, и для разных объектов одного класса возможны несколько сценариев инициализации. by lazy {... }, в свою очередь, определяет единственный инициализатор для свойства, который можно изменить только путем переопределения свойства в подклассе. Если вы хотите, чтобы ваша собственность была инициализирована извне способом, который, возможно, неизвестен заранее, используйте lateinit.

  • Инициализация с by lazy {... } является поточно-ориентированной по умолчанию и гарантирует, что инициализатор вызывается не более одного раза (но это можно изменить с помощью другой lazy перегрузки). В случае lateinit var это зависит от кода пользователя, который правильно инициализирует свойство в многопоточных средах.

  • Экземпляр Lazy можно сохранить, передать и даже использовать для нескольких свойств. Напротив, lateinit var не хранит никакого дополнительного состояния времени выполнения (только null в поле для неинициализированного значения).

  • Если вы isInitialized() ссылку на экземпляр Lazy, isInitialized() позволяет вам проверить, была ли она уже инициализирована (и вы можете получить такой экземпляр с помощью отражения от делегированного свойства). Чтобы проверить, было ли инициализировано свойство lateinit, вы можете использовать property::isInitialized начиная с Kotlin 1.2.

  • Лямбда, переданная by lazy {... } может захватывать ссылки из контекста, в котором она используется, в свое закрытие. Затем она будет хранить ссылки и освобождать их только после инициализации свойства. Это может привести к тому, что иерархии объектов, такие как действия Android, не будут выпускаться слишком долго (или когда-либо, если свойство остается доступным и никогда не будет доступно), поэтому вы должны быть осторожны с тем, что вы используете внутри лямбда-инициализатора.

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

Ответ 2

В дополнение к hotkey хороший ответ, вот как я выбираю среди них на практике:

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

например. по телефону:

private lateinit var value: MyClass

fun init(externalProperties: Any) {
   value = somethingThatDependsOn(externalProperties)
}

Пока lazy - это когда он использует только внутренние запросы, связанные с вашим объектом.

Ответ 3

Очень короткий и краткий ответ

lateinit: в последнее время инициализирует ненулевые свойства

В отличие от отложенной инициализации, lateinit позволяет компилятору распознавать, что значение ненулевого свойства не сохраняется на этапе конструктора для нормальной компиляции.

Ленивая инициализация

Функция lazy может быть очень полезна при реализации свойств только для чтения (val), которые выполняют ленивую инициализацию в Kotlin.

by lazy {...} выполняет его инициализатор, где впервые используется определенное свойство, а не его объявление.

Ответ 4

Кредит идет на @Амит Шекхар

lateinit

lateinit - поздняя инициализация.

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

Пример:

public class Test {

  lateinit var mock: Mock

  @SetUp fun setup() {
     mock = Mock()
  }

  @Test fun test() {
     mock.do()
  }
}

ленивый

ленивая ленивая инициализация.

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

Пример:

public class Example{
  val name: String by lazy { "Amit Shekhar" }
}

Ответ 5

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

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

Используя его правильно, вы можете сократить время загрузки вашего приложения. И реализация Kotlin - это lazy() который загружает необходимое значение в вашу переменную всякий раз, когда это необходимо.

Но lateinit используется, когда вы уверены, что переменная не будет нулевой или пустой и будет инициализирована перед ее использованием -e.g. в onResume() для android-, и поэтому вы не хотите объявлять его как обнуляемый тип.

Ответ 6

Если вы используете контейнер Spring и хотите инициализировать lateinit поле компонента, лучше использовать lateinit.

    @Autowired
    lateinit var myBean: MyBean

Ответ 7

Пример lateinit (что такое поздняя инициализация):

public class Late {

    lateinit var mercedes: Mercedes

    @SetUp fun setup() {
        mercedes = Mercedes()
    }
    @Test fun testing() {
        mercedes.do()
    }
}

Пример lazy (что такое ленивая инициализация):

public class Lazy {

    val name: String by lazy { "Mercedes-Benz" }
}

Ответ 8

Если вы используете неизменяемую переменную, лучше инициализировать с by lazy {... } или val. В этом случае вы можете быть уверены, что он всегда будет инициализирован при необходимости и не более 1 раза.

Если вы хотите lateinit var переменную, которая может изменить ее значение, используйте lateinit var. В разработке для Android вы можете позже инициализировать его в таких событиях, как onCreate, onResume. Имейте в виду, что если вы вызываете запрос REST и получаете доступ к этой переменной, это может привести к исключению UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized, поскольку запрос может выполняться быстрее, чем эта переменная могла бы инициализироваться.

Ответ 9

латинит против ленивых

  1. lateinit

    я) Используйте его с изменяемой переменной [var]

    lateinit var name: String       //Allowed
    lateinit **val** name: String       //Not Allowed
    

    ii) Разрешается только с non- обнуляемыми типами данных

    lateinit **var** name: String       //Allowed
    lateinit **var** name: String?      //Not Allowed
    

    iii) это обещание компилятору, что значение будет инициализировано в будущем.

ПРИМЕЧАНИЕ. Если вы попытаетесь получить доступ к переменной lateinit без ее инициализации, она выдаст исключение UnInitializedPropertyAccessException.

  1. ленивый

    i) Ленивая инициализация была разработана, чтобы предотвратить ненужную инициализацию объектов.

    ii) Ваша переменная не будет инициализирована, если вы ее не используете.

    iii) Инициализируется только один раз. В следующий раз, когда вы его используете, вы получите значение из кеш-памяти.

    iv) Это потокобезопасно (инициализируется в потоке, в котором оно используется впервые. Другие потоки используют то же значение, которое хранится в кэше).

    v) Переменная может быть var или val.

    vi) переменная может быть обнуляемой или non- обнуляемой.