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

目录
  • 前言
    • lateinit
    • by lazy
  • 总结

前言

属性或对象的延时加载是我们相当常用的,一般我们都是使用 lateinit 和 by lazy 来实现。

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

lateinit

见名知意,延时初始化的标记。lateinit var可以让我们声明一个变量并且不用马上初始化,在我们需要的时候进行手动初始化即可。

如果我们不初始化会怎样?

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

会报错:

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

    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 的值之后会有回调的处理。

运行的效果:

除了 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()方法,如下所示:

  • 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 ?

其实大部分情况下都可以通用,只是 by lazy 一般用于非空只读属性,需要延迟加载情况,而 lateinit 一般用于非空可变属性,需要延迟加载情况。

以上就是Kotlin对象的懒加载方式by lazy 与 lateinit 异同详解的详细内容,更多关于Kotlin 对象懒加载by lazy lateinit 的资料请关注我们其它相关文章!

(0)

相关推荐

  • Kotlin修饰符lateinit(延迟初始化)案例详解

    Kotlin定义变量一般有如下写法 lateinit var name: String var age: String? = null 那么用lateinit 修饰和下面那种有什么区别呢,我们来看一下这两行代码反编译成java代码是什么样子的. @NotNull public String name; @Nullable private String age; @NotNull public final String getName() { String var10000 = this.name

  • Kotlin lateinit与by lazy案例详解

    lateinit 和 lazy 是 Kotlin 中的两种不同的延迟初始化的实现 lateinit 只用于变量 var,而 lazy 只用于常量 val lazy 应用于单例模式(if-null-then-init-else-return),而且当且仅当变量被第一次调用的时候,委托方法才会执行. lazy()是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并

  • Kotlin如何安全访问lateinit变量的实现

    Kotlin设计之初就是不允许非null变量在声明期间不进行初始化的,为了解决这个问题,Kotlin lateinit 允许我们先声明一个变量,然后在程序执行周期的将来某个时候将其初始化,让编译检查时不会 因为属性变量未被初始化而报错.如果未初始化将导致以下异常: kotlin.UninitializedPropertyAccessException: lateinit property mList has not been initialized 所以我们在 Kotlin 1.2及更高版本上,

  • 聊聊Kotlin 中 lateinit 和 lazy 的原理区别

    目录 lateinit 用法 原理 lazy 用法 原理 the end references 使用 Kotlin 进行开发,对于 latelinit 和 lazy 肯定不陌生.但其原理上的区别,可能鲜少了解过,借着本篇文章普及下这方面的知识. lateinit 用法 非空类型可以使用 lateinit 关键字达到延迟初始化. class InitTest() { lateinit var name: String ​ public fun checkName(): Boolean = name

  • Kotlin对象的懒加载方式by lazy 与 lateinit 异同详解

    目录 前言 lateinit by lazy 总结 前言 属性或对象的延时加载是我们相当常用的,一般我们都是使用 lateinit 和 by lazy 来实现. 他们两者都是延时初始化,那么在使用时那么他们两者有什么区别呢? lateinit 见名知意,延时初始化的标记.lateinit var可以让我们声明一个变量并且不用马上初始化,在我们需要的时候进行手动初始化即可. 如果我们不初始化会怎样? private lateinit var name: String findViewById<Bu

  • 详解性能更优越的小程序图片懒加载方式

    意义 懒加载或者可以说是延迟加载,针对非首屏或者用户"看不到"的地方延迟加载,有利于页面首屏加载速度快.节约了流量,用户体验好 实现方式 传统H5的懒加载方式都是通过监听页面的scroll事件来实现的,结合viewport的高度来判断. 小程序也类似,通过监听页面onPageScroll事件获取当前滚动的数据,结合getSystemInfo获取设备信息来判断.由于scroll事件密集发生,计算量很大,经常会造成FPS降低.页面卡顿等问题. 这里说的是通过另外一种方式来实现 create

  • IOS中Weex 加载 .xcassets 中的图片资源的实例详解

    IOS中Weex 加载 .xcassets 中的图片资源的实例详解 前言: 因为 .xcassets 中的图片资源只能通过 imageNamed: 方法加载,所以需要做一些特殊处理,才能提供给 Weex 使用(PS:纯属娱乐,因为 Weex 跨平台的特性,这种针对某一端做实现的方案实用价值并不大). 方案 观察 WeexSDK 发现有 WXImgLoaderProtocol 这个协议,这个协议包含了下面的方法: - (id<WXImageOperationProtocol>)downloadI

  • Vue首评加载速度及白屏时间优化详解

    目录 项目背景 splitChunks CDN GZIP 删除文件预加载 项目背景 测试环境的管理后台应客户需求最近加了点东西,加载的东西变多,使得整个项目变得有点臃肿了,首屏以及刷新后渲染速度变得极其缓慢.之前10s左右还能勉强接受,这次一下干到30s,整个人都崩溃了,花了点时间优化了一下. 环境:vue:2.6.11,vue-cli:3.0 splitChunks 看到上面图片里的文件其实并不大,最大的也就287k. 这也是优化过的,之前都是有的最大为1m左右,在vue.config.js配

  • JS实现加载和读取XML文件的方法详解

    本文实例讲述了JS实现加载和读取XML文件的方法.分享给大家供大家参考,具体如下: 有时在开发时用到 JS 加载和读取XML文件的情况,写下提供参考,这里主要是分两步完成: 1. JS加载XML文件 步骤一般为(1),建立 XML DOM 对象:(2),设置加载方式,异步(推荐)或同步: (3)提供XML文件URL然后调用 load 方法:大致如下: var xmlFileName="xxFile.xml"; var xmlDoc=''; if (window.ActiveXObjec

  • 微信小程序 图片加载(本地,网路)实例详解

    在微信小程序中,要显示一张图片,有两种图片加载方式: 加载本地图片 加载网络图片 加载本地图片 <image class="widget__arrow" src="/image/arrowright.png" mode="aspectFill"> </image> src="/image/arrowright.png" 这句就是加载本地图片资源的.想想iOS中的加载本地图片,imageName:,类似.

  • Android异步加载数据和图片的保存思路详解

    把从网络获取的图片数据保存在SD卡上, 先把权限都加上 网络权限 android.permission.INTERNET SD卡读写权限 android.permission.MOUNT_UNMOUNT_FILESYSTEMS android.permission.WRITE_EXTERNAL_STORAGE 总体布局 写界面,使用ListView,创建条目的布局文件,水平摆放的ImageView TextView 在activity中获取到ListView对象,调用setAdapter()方法

  • JVM加载class文件的原理机制实例详解

    目录 一.JVM简介 二.JVM的组成部分 三.JVM加载class文件的原理机制 一.JVM简介 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的. Java语言的一个非常重要的特点就是与平台的 无关性.而使用Java虚拟机是实现这一特点的关键.一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码.而引入Java语言虚拟机后,Java语言在不同

  • js window.onload 加载多个函数和追加函数详解

    平时做项目 经常需要使用window.onload, 用法如下: function func(){alert("this is window onload event!");return;} window.onload=func; 或者如下: window.onload=function(){alert("this is window onload event!");return;} 但window.onload 不能同时加载多个函数. 比如: function t

  • 加载 vue 远程代码的组件实例详解

    在我们的 vue 项目中(特别是后台系统),总会出现一些需要多业务线共同开发同一个项目的场景,如果各业务团队向框架中提供一些私有的展示组件,但是这些组件并不能和框架一起打包,因为框架不能因为某个私有模块的频繁变更而重复构建发布.在这种场景下我们需要一个加载远程异步代码的组件来完成将这些组件加载到框架中. vue-cli 作为 Vue 官方推荐的项目构建脚手架,它提供了开发过程中常用的,热重载,构建,调试,单元测试,代码检测等功能.我们本次的异步远端组件将基于 vue-cli 开发. 需求分析 如

随机推荐