Kotlin – 使用“懒”与“lateinit”的属性初始化

在Kotlin中,如果您不想在构造函数或类体顶部启动类属性,则基本上有以下两个选项(来自语言参考):

  1. 延迟初始化

lazy()是一个函数,它接受一个lambda并返回一个Lazy实例,它可以作为实现一个lazy属性的委托:get()的第一个调用执行传递给lazy()的lambda并记住结果,随后的调用得到()只是返回记忆的结果。

public class Hello{ val myLazyString: String by lazy { "Hello" }` } 

所以第一个调用和次要的调用,无论它在哪里, myLazyString都会返回“Hello”

  1. 延迟初始化

通常,声明为具有非nulltypes的属性必须在构造函数中初始化。 但是,这往往不方便。 例如,属性可以通过dependency injection来初始化,或者在unit testing的设置方法中进行初始化。 在这种情况下,你不能在构造函数中提供一个非null初始值设定项,但是当你引用一个类的内部属性的时候,你还是要避免使用null检查。

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

 public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } } 

修饰符只能用于在类的主体内声明的var属性(不在主构造函数中),只有当属性没有自定义getter或setter时。 属性的types必须是非空的,并且不能是原始types。

那么,如何正确选择这两个选项,既然他们都能解决同样的问题呢?

这里是lateinit varby lazy { ... }委托属性之间的重要区别:

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

  • lateinit var有一个存储值的后台字段, by lazy { ... }创建一个委托对象,在该对象中存储一次计算后的值,将对委托实例的引用存储在类对象中,并为该属性生成getter与委托实例一起工作。 所以如果你需要类中的后台字段,使用lateinit ;

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

  • lateinit var可以从任何地方被初始化,例如从框架代码中看到对象,并且多个初始化场景对于单个类的不同对象是可能的。 by lazy { ... }反过来定义了属性的唯一初始值设定项,只能通过覆盖子类中的属性来改变属性值。 如果您希望您的财产从外部以事先未知的方式初始化,请使用lateinit

  • by lazy { ... }进行初始化是默认的线程安全的,并保证初始化器最多只能调用一次(但可以通过使用另一个lazy重载来更改)。 在lateinit var情况下,由用户的代码在multithreading环境中正确初始化属性。

  • Lazy实例可以保存,传递,甚至用于多个属性。 相反, lateinit var不会存储任何额外的运行时状态(对于未初始化的值,只能为null )。

  • 如果您持有对Lazy实例的引用,则isInitialized()允许您检查它是否已经被初始化(并且可以通过委托属性的reflection来获取此实例 )。 要检查lateinit属性是否已经初始化,可以使用kotlin 1.2以来的property::isInitialized

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

除了hotkey的好的答案之外,下面是我在实践中如何选择这两个:

lateinit是外部初始化:当你需要外部的东西通过调用方法来初始化你的价值。

例如通过呼叫:

 private lateinit var value: MyClass fun init(externalProperties: Any) { value = somethingThatDependsOn(externalProperties) } 

lazy是它只使用对象内部的依赖关系。

信贷去@Amit Shekhar

lateinit

lateinit是后期初始化。

通常,声明为具有非nulltypes的属性必须在构造函数中初始化。 但是,这往往不方便。 例如,属性可以通过dependency injection来初始化,或者在unit testing的设置方法中进行初始化。 在这种情况下,你不能在构造函数中提供一个非null初始值设定项,但是当你引用一个类的内部属性的时候,你还是要避免使用null检查。

例:

 public class Test { lateinit var mock: Mock @SetUp fun setup() { mock = Mock() } @Test fun test() { mock.do() } } 

懒惰是初始化的懒惰。

lazy()是一个函数,它接收一个lambda并返回一个lazy实例,它可以作为实现一个lazy属性的委托:get()的第一个调用执行传递给lazy()的lambda并记住结果,随后的调用得到()只是返回记忆的结果。

例:

 public class Example{ val name: String by lazy { “Amit Shekhar” } } 

如果你使用的是Spring容器,并且你想初始化不可为空的bean字段, lateinit更适合。

 @Autowired lateinit var MyBean myBean 

非常简短的答案

lateinit:最近初始化非空属性

与延迟初始化不同, lateinit允许编译器识别非null属性的值不存储在构造函数阶段中以正常编译。

延迟初始化

当实现在Kotlin中执行延迟初始化的只读 (val)属性时, 通过lazy可能会非常有用。

通过延迟{…}执行其初始化程序,首先使用定义的属性,而不是其声明。