使用"by lazy"和"lateinit"进行属性初始化

摘要

本教程将解释如何使用"by lazy"和"lateinit"在Kotlin中进行属性初始化。原始问答中给出了使用lazy和lateinit的两种不同选项进行属性初始化的方法。我们将对这两种方法进行比较,并解释它们的区别和用途。

内容

引言

在Kotlin中,如果您不想在构造函数中或在类体顶部初始化类属性,您可以使用以下两种选项:lazy初始化和lateinit修饰符。

使用"by lazy"进行延迟初始化

使用"by lazy"语法,可以在第一次访问属性时进行延迟初始化。"by lazy"创建一个Lazy实例,在第一次调用属性的get()方法时执行lambda表达式,并记住结果。随后的调用只会返回记住的结果。

以下是使用"by lazy"进行延迟初始化的示例:

1public class Hello {
2   val myLazyString: String by lazy { "Hello" }
3}

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

使用"lateinit"进行延迟初始化

使用"lateinit"修饰符,可以将属性标记为在类中的其他位置进行延迟初始化。"lateinit"属性必须是可变类型(var),不能是不可为空的类型,且不能是原始类型(如Int、Boolean等)。lateinit属性具有一个后备字段,用于存储值,而"by lazy"则创建一个代理对象,并将值存储在代理对象中。因此,如果需要在类中使用后备字段,请使用"lateinit"。

以下是使用"lateinit"进行延迟初始化的示例:

1public class MyTest {
2   lateinit var subject: TestSubject
3
4   @SetUp fun setup() { subject = TestSubject() }
5
6   @Test fun test() { subject.method() }
7}

需要注意的是,lateinit属性必须在类体中声明,不能在主构造函数中声明,并且不能具有自定义的getter或setter。属性的类型必须是非空的,且不能是原始类型。

如何选择"by lazy"和"lateinit"

在"by lazy"和"lateinit"之间正确选择取决于您的需求。以下是两者的主要区别和用途:

  • "by lazy"只能用于val属性,而"lateinit"只能用于var属性,因为"lateinit"无法编译为final字段,因此无法保证不可变性。
  • "lateinit"有一个后备字段,用于存储值,而"by lazy"在委托对象中存储值。如果需要后备字段,请使用"lateinit"。
  • 除了val属性外,"lateinit"不能用于可为空的属性或Java原始类型。
  • "lateinit var"可以从对象可见的任何位置进行初始化,比如在框架代码内部。对于单个类的不同对象,可以存在多个初始化场景。而"by lazy"定义了属性的唯一初始化器,并且只能通过在子类中重写属性来改变初始化器。如果希望在外部以可能预先未知的方式初始化属性,请使用"lateinit"。
  • "by lazy"的初始化是线程安全的,并且保证初始化器只被调用一次。而对于"lateinit var",在多线程环境下,由用户代码负责正确初始化属性。
  • "Lazy"实例可以保存、传递和用于多个属性。相反,"lateinit var"不存储任何额外的运行时状态,只有一个未初始化值的字段。
  • 如果保持对"Lay"实例的引用,可以使用isInitialized()方法检查属性是否已初始化。要检查lateinit属性是否已初始化,可以使用property::isInitialized(),自Kotlin 1.2版本开始支持该语法。
  • 通过lambda传递给"by lazy"可以捕获上下文中的引用,并将引用存储在闭包中。如果使用了大量的引用,可能导致对象层次结构(如Android活动)在很长时间内不被释放(或者永远不会被访问),因此您应该小心使用初始化器lambda中的内容。

另外,还有一种未在问题中提到的方式:"Delegates.notNull()",这种方式适用于延迟初始化非空属性,包括Java原始类型。


相关文章推荐