properties - 使用 "by lazy"与 "lateinit"进行属性初始化

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

  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" }

}

因此,无论在何处,第一次调用和随后的调用都将发送到 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.

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.

那么,既然这两个选项都可以解决同一个问题,那么如何正确选择呢?

最佳答案

以下是 lateinit varbylazy { ... } 委托(delegate)属性的显着区别:

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

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

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

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

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

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

  • 如果您持有对 Lazy 实例的引用,isInitialized()允许你检查它是否已经被初始化(你可以obtain such instance with reflection从一个委托(delegate)属性)。要检查一个lateinit属性是否已经初始化,可以use property::isInitialized since Kotlin 1.2 .

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

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

关于properties - 使用 "by lazy"与 "lateinit"进行属性初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36623177/

相关文章:

kotlin - 在 Kotlin 中按多个字段对集合进行排序

kotlin - 如何在 Kotlin 中将 String 转换为 Long?

dictionary - 如何在 Kotlin 中将列表转换为 map ?

kotlin - 智能转换为 'Type' 是不可能的,因为 'variable' 是一个可变属性,

collections - Kotlin 的列表缺少 "add"、 "remove"、 map 缺少

arrays - 如何用值初始化 Kotlin 中的数组?

kotlin - 如何将 Kotlin 的 MutableList 初始化为空 MutableLis

inheritance - 在 Kotlin 中扩展数据类

kotlin - Kotlin 中的惯用登录方式

kotlin - 为什么我们使用 "companion object"作为 Kotlin 中 Jav