Kotlin-使用"by lazy"进行属性初始化与"lateinit"的对比 [英] Kotlin - Property initialization using "by lazy" vs. "lateinit"

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

问题描述

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

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()是一个接受lambda并返回Lazy实例的函数,该实例可以充当实现lazy属性的委托:对get()的第一次调用执行传递给lazy()的lambda并记住结果,随后对get()的调用只会返回已记住的结果.

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.

示例

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

因此,对 myLazyString 的第一个调用及其后继调用将返回"Hello"

So the first call and the subquential calls, wherever it is, to myLazyString will return "Hello"

  1. 后期初始化

通常,声明为具有非null类型的属性必须在构造函数中初始化.但是,这经常不方便.例如,可以通过依赖项注入或在单元测试的设置方法中初始化属性.在这种情况下,您不能在构造函数中提供非null的初始值设定项,但在引用类的正文中的属性时仍要避免执行null检查.

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.

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

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() }
}

修饰符只能用于在类主体内声明的var属性(不适用于主构造函数),并且只能在该属性没有自定义getter或setter的情况下使用.该属性的类型必须为非null,并且不能为原始类型.

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 ?

推荐答案

以下是lateinit varby lazy { ... }委派属性之间的重大区别:

Here are the significant differences between lateinit var and by lazy { ... } delegated property:

  • lazy { ... }委托只能用于val属性,而lateinit只能应用于var s,因为它不能编译为final字段,因此没有不变性可以得到保证;

  • 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具有一个用于存储值的后备字段,并且by lazy { ... }创建一个委托对象,一旦计算出值,该对象就存储在其中,将对委托实例的引用存储在类对象中并生成吸气剂与委托实例一起使用的属性.因此,如果您需要类中存在后备字段,请使用lateinit;

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;

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

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

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

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.

初始化by lazy { ... }默认情况下是线程安全的,并保证初始化程序最多被调用一次(但是可以使用

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.

Lazy实例可以保存,传递并甚至用于多个属性.相反,lateinit var不会存储任何其他运行时状态(在未初始化值的字段中仅存储null).

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

如果您拥有对Lazy实例的引用,则获取此类信息委托属性中具有反射的实例).要检查lateinit属性是否已初始化,您可以

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.

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

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.

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

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.

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

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