使用“by lazy"进行属性初始化与“lateinit" [英] Property initialization using "by lazy" vs. "lateinit"

查看:27
本文介绍了使用“by lazy"进行属性初始化与“lateinit"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Kotlin 中,如果您不想在构造函数内部或类主体顶部初始化类属性,您基本上有这两个选项(来自语言参考):

  1. 延迟初始化

<块引用>

lazy() 是一个函数,它接受一个 lambda 并返回一个 Lazy 的实例,它可以作为实现惰性属性的委托:第一个调用 get() 执行传递给 lazy() 的 lambda 并记住结果,随后对 get() 的调用只返回记住的结果.

示例

公共类你好{val myLazyString:由惰性字符串 {Hello";}}

因此,无论在何处,对 myLazyString 的第一次调用和后续调用都将返回 Hello

  1. 后期初始化

<块引用>

通常,声明为非空类型的属性必须在构造函数中初始化.然而,这通常并不方便.例如,可以通过依赖注入或在单元测试的 setup 方法中初始化属性.在这种情况下,您不能在构造函数中提供非空的初始化程序,但您仍然希望在引用类体内的属性时避免空检查.

要处理这种情况,您可以使用 lateinit 修饰符标记该属性:

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

修饰符只能用于在类的主体内(而不是在主构造函数中)声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时.属性的类型必须是非空的,并且不能是原始类型.

那么,如何正确选择这两个选项,因为它们都可以解决同样的问题?

解决方案

以下是 lateinit varby lazy { ... } 委托属性的显着区别:

  • lazy { ... } 委托只能用于 val 属性,而 lateinit 只能用于 vars,因为不能编译成final字段,所以不能保证不变性;

  • lateinit var 有一个存储值的后备字段,并且 by lazy { ... } 创建一个委托对象,其中的值存储一次计算,将委托实例的引用存储在类对象中,并为与委托实例一起使用的属性生成 getter.因此,如果您需要类中存在的支持字段,请使用 lateinit;

  • 除了 val 之外,lateinit 不能用于可空属性或 Java 原始类型(这是因为 null用于未初始化的值);

  • lateinit var 可以从任何可以看到对象的地方初始化,例如从框架代码内部,单个类的不同对象可能有多个初始化场景.by lazy { ... } 反过来,定义了属性的唯一初始化器,它只能通过覆盖子类中的属性来更改.如果您希望您的属性以一种可能事先未知的方式从外部初始化,请使用 lateinit.

  • 初始化 by lazy { ... } 默认情况下是线程安全的,并保证初始化器最多被调用一次(但这可以通过使用 另一个lazy 重载).对于 lateinit var,在多线程环境中正确初始化属性取决于用户的代码.

  • Lazy 实例可以保存、传递,甚至可以用于多个属性.相反,lateinit vars 不存储任何额外的运行时状态(只有 null 在未初始化值的字段中).

  • 如果您持有对 Lazy 实例的引用,isInitialized() 允许你检查它是否已经被初始化(你可以从委托属性中通过反射获取此类实例).要检查是否已初始化 lateinit 属性,您可以 从 Kotlin 1.2 开始使用 property::isInitialized.

  • 由lazy { ... } 传递给 的lambda 可以从上下文中捕获引用,并将其用于closure .. 然后它会存储引用并仅在属性初始化后释放它们.这可能会导致对象层次结构(例如 Android 活动)不会被释放太长时间(或者永远不会被释放,如果该属性仍然可以访问并且从未被访问过),因此您应该小心在初始化程序 lambda 中使用的内容.

另外,问题中没有提到另一种方法:Delegates.notNull(),适用于非空属性的延迟初始化,包括Java基本类型的属性.

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):

  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.

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

  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.

To handle this case, you can mark the property with the lateinit modifier:

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. 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:

  • lazy { ... } delegate can only be used for val properties, whereas lateinit can only be applied to vars, because it can't be compiled to a final field, thus no immutability can be guaranteed;

  • 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. So if you need the backing field present in the class, use lateinit;

  • In addition to vals, lateinit cannot be used for nullable properties or Java primitive types (this is because of null used for uninitialized value);

  • lateinit var can be initialized from anywhere the object is seen from, e.g. from inside a framework code, and multiple initialization scenarios are possible for different objects of a single class. by lazy { ... }, in turn, defines the only initializer for the property, which can be altered only by overriding the property in a subclass. If you want your property to be initialized from outside in a way probably unknown beforehand, use 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). In the case of lateinit var, it's up to the user's code to initialize the property correctly in multi-threaded environments.

  • A Lazy instance can be saved, passed around and even used for multiple properties. On contrary, lateinit vars do not store any additional runtime state (only null in the field for uninitialized value).

  • 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). To check whether a lateinit property has been initialized, you can use property::isInitialized since Kotlin 1.2.

  • 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. 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.

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.

这篇关于使用“by lazy"进行属性初始化与“lateinit"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆