简体   繁体   English

使用“by lazy”与“lateinit”进行属性初始化

[英]Property initialization using "by lazy" vs. "lateinit"

In Kotlin, if you don't want to initialize a class property inside the constructor or in the top of the class body, you have basically these two options (from the language reference):在 Kotlin 中,如果您不想在构造函数内部或 class 主体的顶部初始化 class 属性,您基本上有以下两个选项(来自语言参考):

  1. Lazy Initialization 惰性初始化

lazy() is a function that takes a lambda and returns an instance of Lazy<T> 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. lazy()是一个 function,它接受一个 lambda 并返回一个Lazy<T>实例,它可以作为实现惰性属性的委托:第一次调用get()执行传递给lazy()的 lambda 并记住结果,后续调用get()只会返回记住的结果。

Example例子

public class Hello { val myLazyString: String by lazy { "Hello" } }

So, the first call and the subsequential calls, wherever it is, to myLazyString will return Hello因此,无论在哪里,对myLazyString的第一次调用和后续调用都会返回Hello

  1. Late Initialization 延迟初始化

Normally, properties declared as having a non-null type must be initialized in the constructor.通常,声明为具有非空类型的属性必须在构造函数中初始化。 However, fairly often this is not convenient.然而,这通常并不方便。 For example, properties can be initialized through dependency injection, or in the setup method of a unit test.例如,可以通过依赖注入或单元测试的设置方法来初始化属性。 In this case, 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.在这种情况下,您不能在构造函数中提供非空初始化程序,但在引用 class 主体内的属性时,您仍然希望避免 null 检查。

To handle this case, you can mark the property with the lateinit modifier:要处理这种情况,您可以使用 lateinit 修饰符标记该属性:

 public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } }

The modifier can only 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.该修饰符只能用于在 class 主体内声明的 var 属性(而不是在主构造函数中),并且仅当该属性没有自定义 getter 或 setter 时。 The type of the property must be non-null, and it must not be a primitive type.属性的类型必须是非空的,并且不能是原始类型。

So, how to choose correctly between these two options, since both of them can solve the same problem?那么,既然两者都能解决同一个问题,如何在这两个选项之间做出正确的选择呢?

Here are the significant differences between lateinit var and by lazy { ... } delegated property:以下是lateinit varby lazy { ... }委托属性之间的显着差异:

  • lazy { ... } delegate can only be used for val properties, whereas lateinit can only be applied to var s, because it can't be compiled to a final field, thus no immutability can be guaranteed; lazy { ... }委托只能用于val属性,而lateinit只能用于var s,因为它不能编译为final字段,因此无法保证不变性;

  • lateinit var has a backing field which stores the value, and by lazy { ... } creates a delegate object in which the value is stored once calculated, stores the reference to the delegate instance in the class object and generates the getter for the property that works with the delegate instance. lateinit var有一个存储值的支持字段,并by lazy { ... }创建一个委托对象,一旦计算出该值就存储在该对象中,将委托实例的引用存储在类对象中,并为该属性生成 getter与委托实例一起使用。 So if you need the backing field present in the class, use lateinit ;因此,如果您需要类中存在的支持字段,请使用lateinit

  • In addition to val s, lateinit cannot be used for nullable properties or Java primitive types (this is because of null used for uninitialized value);除了val之外, lateinit不能用于可空属性或 Java 原始类型(这是因为null用于未初始化的值);

  • lateinit var can be initialized from anywhere the object is seen from, eg from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. lateinit var可以从任何可以看到对象的地方初始化,例如从框架代码内部,对于单个类的不同对象可能有多个初始化场景。 by lazy { ... } , in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. by lazy { ... } ,反过来,定义属性的唯一初始化器,只能通过覆盖子类中的属性来更改它。 If you want your property to be initialized from outside in a way probably unknown beforehand, use lateinit .如果您希望您的属性以一种可能事先未知的方式从外部初始化,请使用lateinit

  • Initialization by lazy { ... } is thread-safe by default and guarantees that the initializer is invoked at most once (but this can be altered by using another lazy overload ).默认情况下, by lazy { ... }初始化是线程安全的,并保证初始化程序最多被调用一次(但这可以通过使用 另一个lazy重载来更改)。 In the case of lateinit var , it's up to the user's code to initialize the property correctly in multi-threaded environments.对于lateinit var ,在多线程环境中正确初始化属性取决于用户的代码。

  • A Lazy instance can be saved, passed around and even used for multiple properties. Lazy实例可以保存、传递甚至用于多个属性。 On contrary, lateinit var s do not store any additional runtime state (only null in the field for uninitialized value).相反, lateinit var不存储任何额外的运行时状态(未初始化值的字段中只有null )。

  • If you hold a reference to an instance of Lazy , isInitialized() allows you to check whether it has already been initialized (and you can obtain such instance with reflection from a delegated property).如果您持有对Lazy实例的引用,则isInitialized()允许您检查它是否已被初始化(并且您可以通过委托属性的反射获取此类实例)。 To check whether a lateinit property has been initialized, you can use property::isInitialized since Kotlin 1.2 .要检查是否已初始化 lateinit 属性,您可以使用自 Kotlin 1.2 以来的property::isInitialized

  • A lambda passed to by lazy { ... } may capture references from the context where it is used into its closure .. It will then store the references and release them only once the property has been initialized.传递给by lazy { ... } lambda 可以从上下文中捕获引用,并将其用于其闭包中.. 然后它将存储引用并仅在属性初始化后释放它们。 This may lead to object hierarchies, such as Android activities, not being released for too long (or ever, if the property remains accessible and is never accessed), so you should be careful about what you use inside the initializer lambda.这可能会导致对象层次结构(例如 Android 活动)不会被释放太长时间(或者永远不会释放,如果该属性仍然可访问且从未被访问过),因此您应该小心在初始化程序 lambda 中使用的内容。

Also, there's another way not mentioned in the question: Delegates.notNull() , which is suitable for deferred initialization of non-null properties, including those of Java primitive types.此外,问题中还有另一种方法没有提到: Delegates.notNull() ,它适用于非空属性的延迟初始化,包括 Java 基本类型的属性。

lateinit vs lazy延迟初始化 vs 懒惰

  1. lateinit延迟初始化

    i) Use it with mutable variable[var] i) 与可变变量 [var] 一起使用

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

ii) Allowed with only non-nullable data types ii) 仅允许不可为空的数据类型

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

iii) It is a promise to compiler that the value will be initialized in future. iii) 向编译器承诺该值将在未来被初始化。

NOTE : If you try to access lateinit variable without initializing it then it throws UnInitializedPropertyAccessException.注意:如果您尝试访问lateinit变量而不初始化它,那么它会抛出 UnInitializedPropertyAccessException。

  1. lazy懒惰的

    i) Lazy initialization was designed to prevent unnecessary initialization of objects. i) 延迟初始化旨在防止对象的不必要初始化。

ii) Your variable will not be initialized unless you use it. ii) 你的变量不会被初始化,除非你使用它。

iii) It is initialized only once. iii) 它只被初始化一次。 Next time when you use it, you get the value from cache memory.下次使用它时,您会从缓存中获取值。

iv) It is thread safe(It is initialized in the thread where it is used for the first time. Other threads use the same value stored in the cache). iv)它是线程安全的(它在第一次使用它的线程中初始化。其他线程使用存储在缓存中的相同值)。

v) The variable can only be val . v) 变量只能是val

vi) The variable can only be non- nullable . vi) 变量只能是不可为空的

Additionnally to hotkey 's good answer, here is how I choose among the two in practice:除了hotkey的好答案之外,以下是我在实践中如何在两者中进行选择:

lateinit is for external initialisation: when you need external stuff to initialise your value by calling a method. lateinit用于外部初始化:当您需要外部内容通过调用方法来初始化您的值时。

eg by calling:例如通过调用:

private lateinit var value: MyClass

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

While lazy is when it only uses dependencies internal to your object. lazy是指它只使用对象内部的依赖项。

Very Short and concise Answer非常简短的答案

lateinit: It initialize non-null properties lately lateinit:它最近初始化非空属性

Unlike lazy initialization, lateinit allows the compiler to recognize that the value of the non-null property is not stored in the constructor stage to compile normally.与延迟初始化不同, lateinit允许编译器识别非空属性的值未存储在构造函数阶段以正常编译。

lazy Initialization延迟初始化

by lazy may be very useful when implementing read-only (val) properties that perform lazy-initialization in Kotlin.在 Kotlin 中实现执行惰性初始化的只读(val) 属性时, by lazy可能非常有用。

by lazy { ... } performs its initializer where the defined property is first used, not its declaration. by lazy { ... } 在第一次使用定义的属性的地方执行它的初始化程序,而不是它的声明。

In addition to all of the great answers, there is a concept called lazy loading:除了所有出色的答案之外,还有一个称为延迟加载的概念:

Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed.延迟加载是计算机编程中常用的一种设计模式,用于将对象的初始化推迟到需要的时候。

Using it properly, you can reduce the loading time of your application.正确使用它,您可以减少应用程序的加载时间。 And Kotlin way of it's implementation is by lazy() which loads the needed value to your variable whenever it's needed. Kotlin 的实现方式是通过lazy()将所需的值加载到您的变量中。

But lateinit is used when you are sure a variable won't be null or empty and will be initialized before you use it -eg in onResume() method for android- and so you don't want to declare it as a nullable type.但是当您确定变量不会为空或为空并且将在您使用它之前初始化时使用 lateinit - 例如在 android 的onResume()方法中 - 因此您不想将其声明为可空类型。

Diff btw lateinit and lazy顺便说一下,lateinit 和lazy 的区别

lateinit延迟初始化

  1. Use only with mutable variable ie var and non-nullable data types仅用于可变变量,即 var 和不可为空的数据类型

lateinit var name: String //Allowed with non-nullable lateinit var name: String //允许不可为空

  1. You are telling the compiler that the value will be initialized in future.您告诉编译器该值将在将来被初始化。

NOTE: If you try to access lateinit variable without initializing it then it throws UnInitializedPropertyAccessException.注意:如果您尝试访问 lateinit 变量而不初始化它,那么它会抛出 UnInitializedPropertyAccessException。

lazy懒惰的

  1. Lazy initialization was designed to prevent unnecessary initialization of objects.延迟初始化旨在防止对对象进行不必要的初始化。

  2. Your variable will not be initialized unless you use it.除非您使用它,否则您的变量不会被初始化。

  3. It is initialized only once.它只初始化一次。 Next time when you use it, you get the value from cache memory.下次使用它时,您会从缓存中获取值。

  4. It is thread safe.它是线程安全的。

  5. The variable can only be val and non-nullable.变量只能是 val 且不可为空。

Cheers :)干杯:)

Everything is correct above, but one of facts simple explanation LAZY ----There are cases when you want to delay the creation of an instance of your object until its first usage.上面一切都是正确的,但一个事实简单解释LAZY ----在某些情况下,您希望将对象实例的创建延迟到第一次使用。 This technique is known as lazy initialization or lazy instantiation.这种技术称为惰性初始化或惰性实例化。 The main purpose of lazy initialization is to boost performance and reduce your memory footprint.延迟初始化的主要目的是提高性能并减少内存占用。 If instantiating an instance of your type carries a large computational cost and the program might end up not actually using it, you would want to delay or even avoid wasting CPU cycles.如果实例化您的类型的实例需要大量的计算成本并且程序最终可能不会实际使用它,您可能希望延迟甚至避免浪费 CPU 周期。

Credit goes to @Amit Shekhar 归功于@Amit Shekhar

lateinit lateinit

lateinit is late initialization. lateinit是迟到的初始化。

Normally, properties declared as having a non-null type must be initialized in the constructor. 通常,必须在构造函数中初始化声明为具有非null类型的属性。 However, fairly often this is not convenient. 但是,这通常不方便。 For example, properties can be initialized through dependency injection, or in the setup method of a unit test. 例如,可以通过依赖注入或单元测试的设置方法初始化属性。 In this case, 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. 在这种情况下,您无法在构造函数中提供非null初始值设定项,但在引用类体内的属性时仍希望避免空值检查。

Example: 例:

public class Test {

  lateinit var mock: Mock

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

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

lazy

lazy is lazy initialization. 懒惰是初始化懒惰。

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. lazy()是一个函数,它接受一个lambda并返回一个lazy实例,它可以作为实现一个惰性属性的委托:第一次调用get()执行传递给lazy()的lambda并记住结果,后续调用get()只返回记住的结果。

Example: 例:

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

If you are using Spring container and you want to initialize non-nullable bean field, lateinit is better suited.如果您使用的是 Spring 容器并且想要初始化不可为 null 的 bean 字段, lateinit适合使用lateinit

    @Autowired
    lateinit var myBean: MyBean

If you use an unchangable variable, then it is better to initialize with by lazy { ... } or val .如果您使用不可更改的变量,那么最好by lazy { ... }val进行初始化。 In this case you can be sure that it will always be initialized when needed and at most 1 time.在这种情况下,您可以确保它总是在需要时初始化,最多 1 次。

If you want a non-null variable, that can change it's value, use lateinit var .如果您想要一个可以更改其值的非空变量,请使用lateinit var In Android development you can later initialize it in such events like onCreate , onResume .在 Android 开发中,您可以稍后在诸如onCreateonResume类的事件中对其进行初始化。 Be aware, that if you call REST request and access this variable, it may lead to an exception UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized , because the request can execute faster than that variable could initialize.请注意,如果您调用 REST 请求并访问此变量,则可能会导致异常UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized ,因为请求的执行速度比该变量的初始化速度快。

The Lateinit and Lazy initializations are two initialization properties in the Kotlin language. LateinitLazy初始化是 Kotlin 语言中的两个初始化属性。

When To Use Lateinit何时使用 Lateinit

  • to initialize a variable late.迟到初始化一个变量。 when you are sure about initializing a variable before using it.当您确定要在使用之前初始化变量时。 with the var keyword.使用 var 关键字。
  • if variables change at a later stage, ie, if the variable is mutable.如果变量在后期发生变化,即变量是否可变。 Lateinit variables can be declared inside the class. Lateinit 变量可以在类中声明。
  • Lateinit does not allocate memory before initializing. Lateinit 在初始化之前不分配内存。

What to avoid while using Lateinit使用 Lateinit 时应避免什么

  • While using Lateinit, the variable can't be of the null type.使用 Lateinit 时,变量不能为空类型。

  • Lateinit cannot be used for non-primitive datatypes, ie, Long and int. Lateinit 不能用于非原始数据类型,即 Long 和 int。

  • If you try accessing Lateinit variables without initializing, it will throw an exception stating that it is not initialized or properly being accessed.如果您尝试在未初始化的情况下访问 Lateinit 变量,它将抛出一个异常,指出它未初始化或被正确访问。

  • It can, later on, be initialized它可以稍后被初始化

private lateinit var lateUri : Uri

When to use Lazy initialization何时使用延迟初始化

  • In the Lazy initialization, your variable will not be initialized unless you call/use it.在延迟初始化中,除非您调用/使用它,否则不会初始化您的变量。

  • The Lazy initialization initializes the variable once; Lazy 初始化将变量初始化一次; that same value is then used throughout the code.然后在整个代码中使用相同的值。

  • It is used for read-only properties as the same valued variable is used throughout.它用于只读属性,因为始终使用相同的值变量。

  • This initialization is used in the case of the val property.此初始化用于 val 属性的情况。

  • It is preferred when the variable is to be shared by all and only当变量被所有人共享时是首选
    initialized once.初始化一次。

  • It can be used when an object is dependent on a variable internal to the class.当对象依赖于类内部的变量时,可以使用它。

What to avoid while using the Lazy initialization使用 Lazy 初始化时应避免什么

  • The code spreads throughout the class at an undecided time, which can lead to confusion.代码在不确定的时间传播到整个班级,这可能会导致混乱。

  • The Lazy object returns the previously initialized value when accessed later. Lazy 对象在稍后访问时返回先前初始化的值。

  • The Lazy Initialization causes memory leakage when used on a retained fragment as it holds a reference to the old view.延迟初始化在保留片段上使用时会导致内存泄漏,因为它持有对旧视图的引用。

    val string: String by lazy {val text = "this value"}

Lateinit vs. Lazy initialization Lateinit 与延迟初始化

  • In case a property does not have a custom setter and getter, Lateinit is used.如果属性没有自定义的 setter 和 getter,则使用 Lateinit。 In a mult-threaded environment, Lateinit initialization is dependent on the user.在多线程环境中,Lateinit 初始化依赖于用户。
  • The Lazy initialization is thread-safe.延迟初始化是线程安全的。
  • Lateinit can only be used with var. Lateinit 只能与 var 一起使用。
  • Lazy initialization is used with the val property.延迟初始化与 val 属性一起使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM