使用“by lazy"进行属性初始化与“lateinit" [英] Property initialization using "by lazy" vs. "lateinit"
问题描述
在 Kotlin 中,如果您不想在构造函数内部或类主体顶部初始化类属性,您基本上有这两个选项(来自语言参考):
<块引用>
lazy()
是一个函数,它接受一个 lambda 并返回一个 Lazy
的实例,它可以作为实现惰性属性的委托:第一个调用 get()
执行传递给 lazy()
的 lambda 并记住结果,随后对 get()
的调用只返回记住的结果.
示例
公共类你好{val myLazyString:由惰性字符串 {Hello";}}
因此,无论在何处,对 myLazyString
的第一次调用和后续调用都将返回 Hello
<块引用>
通常,声明为非空类型的属性必须在构造函数中初始化.然而,这通常并不方便.例如,可以通过依赖注入或在单元测试的 setup 方法中初始化属性.在这种情况下,您不能在构造函数中提供非空的初始化程序,但您仍然希望在引用类体内的属性时避免空检查.
要处理这种情况,您可以使用 lateinit 修饰符标记该属性:
public class MyTest {lateinit var 主题:TestSubject@SetUp fun setup() { subject = TestSubject() }@Test fun test() { subject.method() }}
修饰符只能用于在类的主体内(而不是在主构造函数中)声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时.属性的类型必须是非空的,并且不能是原始类型.
那么,如何正确选择这两个选项,因为它们都可以解决同样的问题?
以下是 lateinit var
和 by lazy { ... }
委托属性的显着区别:
lazy { ... }
委托只能用于val
属性,而lateinit
只能用于var
s,因为不能编译成final
字段,所以不能保证不变性;lateinit var
有一个存储值的后备字段,并且by lazy { ... }
创建一个委托对象,其中的值存储一次计算,将委托实例的引用存储在类对象中,并为与委托实例一起使用的属性生成 getter.因此,如果您需要类中存在的支持字段,请使用lateinit
;除了
val
之外,lateinit
不能用于可空属性或 Java 原始类型(这是因为null
用于未初始化的值);lateinit var
可以从任何可以看到对象的地方初始化,例如从框架代码内部,单个类的不同对象可能有多个初始化场景.by lazy { ... }
反过来,定义了属性的唯一初始化器,它只能通过覆盖子类中的属性来更改.如果您希望您的属性以一种可能事先未知的方式从外部初始化,请使用lateinit
.初始化
by lazy { ... }
默认情况下是线程安全的,并保证初始化器最多被调用一次(但这可以通过使用 另一个lazy
重载).对于lateinit var
,在多线程环境中正确初始化属性取决于用户的代码.Lazy
实例可以保存、传递,甚至可以用于多个属性.相反,lateinit var
s 不存储任何额外的运行时状态(只有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):
lazy()
is a function that takes a lambda and returns an instance ofLazy<T>
which can serve as a delegate for implementing a lazy property: the first call toget()
executes the lambda passed tolazy()
and remembers the result, subsequent calls toget()
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
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 forval
properties, whereaslateinit
can only be applied tovar
s, because it can't be compiled to afinal
field, thus no immutability can be guaranteed;lateinit var
has a backing field which stores the value, andby 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, uselateinit
;In addition to
val
s,lateinit
cannot be used for nullable properties or Java primitive types (this is because ofnull
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, uselateinit
.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 anotherlazy
overload). In the case oflateinit 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 var
s do not store any additional runtime state (onlynull
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 useproperty::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屋!