Kotlin – 使用“懒”与“lateinit”的属性初始化
在Kotlin中,如果您不想在构造函数或类体顶部启动类属性,则基本上有以下两个选项(来自语言参考):
- 延迟初始化
lazy()是一个函数,它接受一个lambda并返回一个Lazy实例,它可以作为实现一个lazy属性的委托:get()的第一个调用执行传递给lazy()的lambda并记住结果,随后的调用得到()只是返回记忆的结果。
例
public class Hello{ val myLazyString: String by lazy { "Hello" }` }
所以第一个调用和次要的调用,无论它在哪里, myLazyString都会返回“Hello”
- 延迟初始化
通常,声明为具有非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 var
和by 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可能会非常有用。
通过延迟{…}执行其初始化程序,首先使用定义的属性,而不是其声明。