首先:
The two models are similar, and one predates the other. Delegates.notNull() (api reference) is based on delegated properties and was the original, and later came lateinit (Late Initialized Properties). Neither cover all possible use cases and neither should be used unless you can control the lifecycle of the class and know for sure that they will be initialized before being used.
If the backing field might be set directly, or your library cannot work with a delegate then you should use lateinit and typically it is the default for most people when using with dependency injection. From the docs:
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.
If the type you are using is not supported by lateinit (does not support primitive types) you are then forced to use the delegate.
The (lateinit) 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.
You might also want to read the discussion topic "Improving lateinit".
译文
这两个模型是相似的,Delegates.notNull()(api参考)基于委托属性,是原始的,后来是lateinit(后期初始化属性)。不覆盖所有可以能的用例,除非你能够控制类的生命周期,并确保它们在使用前初始化。
如果可以以直接设置备份字段,或者者你的库不能使用委托,那么你应该使用lateinit。来自文档的:
通常,声明为非空类型的属性必须在构造函数中初始化。然而,这通常是不方便的。例如可以通过依赖项注入或者单元测试的设置方法来初始化属性。在这种情况下,不能在构造函数中提供非空初始值设定项,但是在引用类中的属性时仍然要避免null检查。
如果lateinit(不支持基元类型)不支持你正在使用的类型,则强制你使用委托。
(lateinit)修饰符只能在类(不在主构造函数中)内声明的var属性上使用,而且只有在该属性没有自定义集合或者设置器时。属性的类型必须是非空的,并且不能是基元类型。
Delegates.notNull()的使用举例
Delegate.notNull()代理主要用于可以不在构造器初始化时候初始化而是可以延迟到之后再初始化这个var修饰的属性,它和lateinit功能类似,但是也有一些不同,不过它们都需要注意的一点是属性的生命周期,开发者要做到可控,也就是一定要确保属性初始化是在属性使用之前,否则会抛出一个IllegalStateException。
package com.mikyou.kotlin.delegate
import kotlin.properties.Delegates
class Teacher {
var name: String by Delegates.notNull()
}
fun main(args: Array<String>) {
val teacher = Teacher().apply { name = "Mikyou" }
println(teacher.name)
}
对比分析:
可能有的人并没有看到*notNull()*有什么大的用处,先说下大背景吧就会明白它的用处在哪了?
大背景: 在Kotlin开发中与Java不同的是在定义和声明属性时必须要做好初始化工作,否则编译器会提示报错的,不像Java只要定义就OK了,管你是否初始化呢。我解释下这也是Kotlin优于Java地方之一,没错就是空类型安全,就是Kotlin在写代码时就让你明确一个属性是否初始化,不至于把这样的不明确定义抛到后面运行时。如果在Java你忘记了初始化,那么恭喜你在运行时你就会拿到空指针异常。
问题来了: 大背景说完了那么问题也就来了,相比Java,Kotlin属性定义时多出了额外的属性初始化的工作。但是可能某个属性的值在开始定义的时候你并不知道,而是需要执行到后面的逻辑才能拿到。这时候解决方式大概有这么几种:
- 方式A: 开始初始化的时给属性赋值个默认值
- 方式B: 使用Delegates.notNull()属性代理
- 方式C: 使用lateinit修饰属性
以上三种方式有局限性,方式A就是很暴力直接赋默认值,对于基本类型还可以,但是对于引用类型的属性,赋值一个默认引用类型对象就感觉不太合适了。方式B适用于基本数据类型和引用类型,但是存在属性初始化必须在属性使用之前为前提条件。方式C仅仅适用于引用类型,但是也存在属性初始化必须在属性使用之前为前提条件。
属性使用方式 | 优点 | 缺点 |
---|---|---|
方式A(初始化赋默认值) | 使用简单,不存在属性初始化必须在属性使用之前的问题 | 仅仅适用于基本数据类型 |
方式B(Delegates.notNull()属性代理) | 适用于基本数据类型和引用类型 | 1、存在属性初始化必须在属性使用之前的问题; 2、不支持外部注入工具将它直接注入Java字段中 |
方式C(lateinit修饰属性) | 仅适用于引用类型 | 1、存在属性初始化必须在属性使用之前的问题; 2、不支持基本数据类型 |
使用建议: 如果能对属性生命周期做很好把控的话,且不存在注入到外部字段需求,建议使用方式B;此外还有一个不错建议就是方式A+方式C组合,或者方式A+方式B组合。具体看实际场景需求。
Delegates.observable()的基本使用
Delegates.observable()主要用于监控属性值发生变更,类似于一个观察者。当属性值被修改后会往外部抛出一个变更的回调。它需要传入两个参数,一个是initValue初始化的值,另一个就是回调lamba, 回调出property, oldValue, newValue三个参数。
package com.mikyou.kotlin.delegate
import kotlin.properties.Delegates
class Person{
var address: String by Delegates.observable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
println("property: ${property.name} oldValue: $oldValue newValue: $newValue")
})
}
fun main(args: Array<String>) {
val person = Person().apply { address = "ShangHai" }
person.address = "BeiJing"
person.address = "ShenZhen"
person.address = "GuangZhou"
}
运行结果
property: address oldValue: NanJing newValue: ShangHai
property: address oldValue: ShangHai newValue: BeiJing
property: address oldValue: BeiJing newValue: ShenZhen
property: address oldValue: ShenZhen newValue: GuangZhou
Process finished with exit code 0
Delegates.vetoable()的基本使用
Delegates.vetoable()代理主要用于监控属性值发生变更,类似于一个观察者,当属性值被修改后会往外部抛出一个变更的回调。它需要传入两个参数,一个是initValue初始化的值,另一个就是回调lamba, 回调出property, oldValue, newValue三个参数。与observable不同的是这个回调会返回一个Boolean值,来决定此次属性值是否执行修改。
package com.mikyou.kotlin.delegate
import kotlin.properties.Delegates
class Person{
var address: String by Delegates.vetoable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
println("property: ${property.name} oldValue: $oldValue newValue: $newValue")
return@vetoable newValue == "BeiJing"
})
}
fun main(args: Array<String>) {
val person = Person().apply { address = "NanJing" }
person.address = "BeiJing"
person.address = "ShangHai"
person.address = "GuangZhou"
println("address is ${person.address}")
}