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

}

所以,第一次调用和后续调用,无论它在哪里,都会返回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.

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

public class MyTest {
   
   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

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

那么,既然这两个选项都能解决同一个问题,那么如何在这两个选项之间做出正确的 Select 呢?

推荐答案

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有一个存储值的后备字段,by lazy { ... }创建一个Delegate对象,一旦计算出该值就存储在其中,将对委托实例的引用存储在类对象中,并为处理该委托实例的属性生成getter.因此,如果您需要在类中提供支持字段,请使用lateinit

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

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

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

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

  • 如果您持有对Lazy实例的引用,则isInitialized()允许您判断它是否已经初始化(并且您可以从委托属性obtain such instance with reflection).若要判断lateinit属性是否已初始化,您可以 Select 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.

Kotlin相关问答推荐

了解Kotlin函数

使用数据存储首选项Kotlin Jetpack Compose

为什么在Spring中,对事务性方法调用的非事务方法调用仍然在事务中运行?

何时使用figureEach

插入/更新/upsert时不发出房间流

如何接受任何派生类KClass

使用 StateFlow 时如何移除监听器?

返回 kotlin 中的标签和 lambda 表达式

循环中的每个元素都应填充行中可用的所有可用空间

Kotlin 中的密封和内部有什么区别?

KMM:编译失败:意外的 IrType 类型:KIND_NOT_SET

致命错误 LifecycleOwners 必须在 registerForActivityResult 开始之前调用 register

有没有办法在spring webflux和spring data react中实现分页

在 Spring Framework 5.1 中注册具有相同名称的测试 bean

android Room 将 Null 作为非 Null 类型返回

Android:Exoplayer - ExtractorMediaSource 已弃用

访问Kotlin中的属性委托

Kotlin:测试中的 java.lang.NoSuchMethodError

Android studio,构建kotlin时出现奇怪错误:生成错误代码

为什么在 Kotlin 中return可以返回一个return?