继续创造,加快成长!这是我参与「日新方案 10 月更文应战」的第8天,点击查看活动详情

前言

特点或目标的延时加载是咱们适当常用的,一般咱们都是运用 lateinit 和 by lazy 来完成。

他们两者都是延时初始化,那么在运用时那么他们两者有什么区别呢?

lateinit

见名知意,延时初始化的标记。lateinit var能够让咱们声明一个变量而且不用马上初始化,在咱们需求的时分进行手动初始化即可。

假如咱们不初始化会怎样?

    private lateinit var name: String
    findViewById<Button>(R.id.btn_load).click {
        YYLogUtils.w("name:$name age:$age")
    }

会报错:

Kotlin对象的懒加载方式?by lazy 与 lateinit 的异同

所以对应这一种状况咱们会有一个是否初始化的判断

    private lateinit var name: String
    findViewById<Button>(R.id.btn_load).click {
        if (this::name.isInitialized) {
           YYLogUtils.w("name:$name age:$age")
        }   
    }

lateinit var的作用相对较简略,其实便是让编译期在检查时不要由于特点变量未被初始化而报错。(注意一定要记得初始化哦!)

by lazy

by lazy 托付延时处理,分为托付和延时

其实假如咱们不想延时初始化,咱们直接运用托付by也能够完成。

   private var age: Int by Delegates.observable(18) { property, oldValue, newValue ->
        YYLogUtils.w("产生了回调 property:$property oldValue:$oldValue newValue:$newValue")
    }
    findViewById<Button>(R.id.btn_load).click {
        age = 25
        YYLogUtils.w("name:$name age:$age")
    }

咱们经过 by Delegates 的办法就能够指定托付目标,这儿我用的 Delegates.obsevable 它的作用是修正 age 的值之后会有回调的处理。

运转的效果:

Kotlin对象的懒加载方式?by lazy 与 lateinit 的异同

除了 Delegates.obsevable 它还有其他的用法。

public object Delegates {
    public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()
    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }
    public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
        }
}
private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null
    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }
    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}
  • notNull办法咱们能够看到便是说这个目标不能为null,不然就会抛出异常。
  • observable办法主要用于监控特点值产生改变,类似于一个观察者。当特点值被修正后会往外部抛出一个改变的回调。
  • vetoable办法跟observable类似,都是用于监控特点值产生改变,当特点值被修正后会往外部抛出一个改变的回调。与observable不同的是这个回调会回来一个Boolean值,来决定此次特点值是否履行修正。

其实用不用托付没什么区别,便是看是否需求特点变化的回调监听,不然咱们直接用变量即可

   private var age: Int  = 18
    findViewById<Button>(R.id.btn_load).click {
        age = 25
        YYLogUtils.w("name:$name age:$age")
    }

假如咱们想完成延时初始化的关键便是 lazy 关键字,所以,lazy是如何作业的呢? 让咱们一同在Kotlin规范库参考中总结lazy()办法,如下所示:

Kotlin对象的懒加载方式?by lazy 与 lateinit 的异同

  • lazy() 回来的是一个存储在lambda初始化器中的Lazy类型实例。
  • getter的第一次调用履行传递给lazy()的lambda并存储其成果。
  • 后边再调用的话,getter调用只回来存储中的值。

简略地说,lazy创立一个实例,在第一次拜访特点值时履行初始化,存储成果并回来存储的值。

   private val age: Int by lazy { 18 / 2 }
    findViewById<Button>(R.id.btn_load).click {
        age = 25
        YYLogUtils.w("name:$name age:$age")
    }

由于咱们运用的是 by lazy ,归根结底还是一种托付,仅仅它是一种特别的托付,它的过程是这样的:

咱们的特点 age 需求 by lazy 时,它生成一个该特点的附加特点:age?delegate。 在构造器中,将运用 lazy(()->T) 创立的 Lazy 实例目标赋值给 age?delegate。 当该特点被调用,即其getter办法被调用时回来 age?delegate.getVaule(),而 age?delegate.getVaule()办法的回来成果是目标 age?delegate 内部的 _value 特点值,在getVaule()第一次被调用时会将_value进行初始化并储存起来,往后都是直接将_value的值回来,从而完成特点值的唯一一次的初始化,并无法再次修正。所以它是只读的。

当咱们调用这个 age 这个特点的时分才会初始化,它归于一种懒加载,既然是懒加载,就必然涉及到线程安全的问题,咱们看看lazy是怎么解决的。

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }
public actual fun <T> lazy(lock: Any?, initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer, lock)    

咱们需求考虑的是线程安全和非线程安全

  • SYNCHRONIZED经过加锁来确保只有一个线程能够初始化Lazy实例,是线程安全的

  • PUBLICATION表明不加锁,能够并发拜访多次调用,但是我之接纳第一个回来的值作为Lazy的实例,其他后边回来的是啥玩意儿我不论。这也是线程安全的

  • NONE不加锁,是线程不安全的

总结

总的来说其实 lateinit 是推延初始化, by lazy 是懒加载即初始化办法已确认,仅仅在运用的时分履行。

虽然两者都能够推延特点初始化的时间,但是 lateinit var 仅仅让编译期疏忽对特点未初始化的检查,后续在哪里以及何时初始化还需求开发者自己决定。而by lazy真正做到了声明的一起也指定了推延初始化时的行为,在特点被第一次被运用的时分能自动初始化。

而且 lateinit 是可读写的,by lazy 是只读的。

那咱们什么时分该运用 lateinit,什么时分运用 by lazy ?

其实大部分状况下都能够通用,什么状况下不主张通用。

  1. by lazy 一般用于非空只读特点,需求推延加载状况,而 lateinit 一般用于非空可变特点,需求推延加载状况。
  2. 生命周期相关的赋值操作,没必要运用by lazy,直接运用成员变量或许 lateinit 。在生命周期中直接运用 by lazy 反倒有可能产生负优化。特别是在 onCreate 中的延时初始化,彻底没必要。
  3. 耗时的赋值操作慎重运用 by lazy 。默许的lazy即同步锁,而Android默许线程为UI主线程,这样就可能导致主线程的卡顿。所以推荐运用协程和Flow来操作此类场景。

Ok,差不多就讲到这儿了,

惯例,如有错漏还请指出,假如有更好的方案也欢迎留言区沟通。

假如感觉本文对你有一点点的启示,还望你能点赞支持一下,你的支持是我最大的动力。

Ok,这一期就此完结。

Kotlin对象的懒加载方式?by lazy 与 lateinit 的异同