Kotlin中lateinit和by lazy的区别
区别
从2者的概念上来区分,lateinit是修饰变量和属性,by lazy 是2个单词组成,其中by 是关键字,lazy 是一个函数。
先看看下面的代码,就会很清楚的理解2者之间的区别了。
lateinit
由于kotlin有严格的语法要求变量需要声明是否可以为null,但由于在实际的业务场景中,这个变量必须在某些时候才能做初始化操作,并且这个变量肯定不为null,如果为null,就是逻辑有问题了。这个时候可以使用lateinit来修饰这个变量。如果没有初始化就使用这个变量,那么就会抛出异常。
lateinit的使用
class LateInitExample {
lateinit var value:String
}
fun main() {
val example = LateInitExample()
// 如果没有赋值就使用,直接抛出异常。
example.value = "lateinit example"
println("${example.value}")
}
lateinit的具体实现
lateinit可以达到这样的效果,其实非常简单。类似于如下的java代码。
public class LateInit {
private String value;
public String getValue() {
// 如果没有初始化过,就抛出异常
if (value == null){
throw new RuntimeException("lateinit property value has not been initialized");
}
return value;
}
public void setValue(String value) {
// 这里要做非null检查
this.value = value;
}
}
by lazy
前面说了,by 和 lazy要单独拿出来看,不能当做一个整体来看。
by:这里涉及到了kotlin的委托中委托属性。
lazy:一个kotlin的函数
by和委托属性
先简单看一下如果实现委托属性。
import kotlin.reflect.KProperty
class DelegateExample {
var name:String by Delegate()
}
class Delegate{
private var _name:String = "default value provide by Delegate"
operator fun getValue(example: DelegateExample, property: KProperty<*>): String {
println("Delegate : get Value")
return _name
}
operator fun setValue(example: DelegateExample, property: KProperty<*>, s: String) {
println("Delegate : set Value: $s")
_name = s
}
}
fun main() {
val example = DelegateExample();
println(example.name)
example.name = "tom"
println(example.name)
}
输出结果:
Delegate : get Value
default value provide by Delegate
Delegate : set Value: tom
Delegate : get Value
tom
简单通俗理解就是这个变量的get,set都是委托给了另外一个类来去操作。
如果是var变量,必须要有getValue和setValue2个方法,val变量不需要setValue方法。
语法是: val/var <属性名>: <类型> by <表达式>
Kotlin 标准库为几种有用的委托提供了工厂方法,延迟属性 Lazy就是其中之一。
lazy
已经知道了by来干什么的,具体怎么用by,我们先来看看by lazy如何使用。
class ByLazyExample {
val name:String by lazy {
println("get name by lazy")
"tom"
}
}
fun main() {
val example = ByLazyExample()
println(example.name)
println(example.name)
println(example.name)
}
输出结果:
get name by lazy
tom
tom
tom
我们发现get name by lazy只执行了一次,只有第一次取得时候执行了lazy的代码块。所以by lazy可以做到延迟初始化,等需要的时候再去初始化,并且只会执行一次代码块。
模仿一下lazy的实现
代码块这里是使用了lambda表达式。我们先尝试着根据我们刚才学习的by自己实现一个可以做到上面输出。在上面的DelegateExample中我们已经使用了by,但是我们的get会执行多次,我们试着更新一下上面的代码,模仿一下lazy。
import kotlin.reflect.KProperty
class DelegateExample {
val name:String by delegate {
println("get name by lazy")
"tom"
}
}
fun delegate(init : () -> String):Delegate{
return Delegate(init)
}
class Delegate{
private val _init : () -> String
constructor(init : () -> String){
_init = init
}
private var _name:String = "default value provide by Delegate"
private var _nameIsLoad:Boolean = false;
operator fun getValue(example: DelegateExample, property: KProperty<*>): String {
if (_nameIsLoad){
return _name;
}
_name = _init()
_nameIsLoad = true
return _name
}
}
fun main() {
val example = DelegateExample();
println(example.name)
println(example.name)
println(example.name)
}
输出结果:
get name by lazy
tom
tom
tom
这样看着我们写的by delegate和by lazy很像啊。实际上,这个和by lazy的实现真的很像,只是很粗糙。
我们先看一下我们更新了DelegateExample哪些东西,
- 首先定义了一个函数,返回值是Delegate
- Delegate接收一个函数类型参数,新增了一个Boolean值来判断是否已经赋值过了。
- getValue的时候判断如果没有赋值,就通过传递进来的函数赋值,初始化。
- DelegateExample的name变量由var变成了val,这个很好理解,因为既然name的赋值都交给我们自己的lambda来做了,那么setValue就不需要了。这也是为什么kotlin的by lazy不支持var的原因,因为没有了setValue方法实现。
lazy的实现
根据上面我们写的代码,应该基本上对lazy的实现已经清楚了。接下来看一下kotlin是如果实现lazy的。
下面贴出部分代码。
// 跟我们刚才写的很像,有个方法,参数是一个lambda表达式,返回一个Lazy对象
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
// Lazy接口
public interface Lazy<out T> {
public val value: T
}
// Lazy的实现类
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
private val lock = lock ?: this
// 采用double check的方式处理value的获取。保证多线程情况下代码块也只会被执行一次。
override val value: T
get() {
val _v1 = _value
// 采用不是默认值就证明赋值了,不是我们上面自己实现的Boolean值记录。
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
// 执行代码块
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
看到这里,相信大家只剩下一个疑惑了,为什么没有getValue方法,不是说by的实现需要getValue方法吗?这里,kotlin使用了扩展函数来做。
// 这里返回value,就会执行实现类的override value get了。
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
总结
看完上面的代码,相信已经把lateinit和by lazy解释清楚了。
- 2者的概念不同,一个是延迟初始化,一个是委托属性。
- by lazy只能用在val声明的变量上,为什么上面代码也解释了,并且是线程安全的。
初学,有不对的地方欢迎大家指出,互相学习。