Kotlin 中的 Lazy vs Lateinit 解析

Kotlin 中的 Lazy vs Lateinit 解析

hi!Kotlin 开发爱好者们 类的属性是每个代码库的基础部分。让我们看看一些初始化它们的方法,以简化代码并提高效率和可读性!系好安全带,开车!

根据你的需求,你可能希望你的属性是不可变的 val 或可变的 var。开发者经常在稍后在代码中初始化属性时遇到困难。这就是为什么在 Kotlin 中,我们有 lazy 和 lateinit 属性初始化器。

by lazy()

lazy 只能用于 val。顾名思义,属性是延迟初始化的,这意味着它将在首次使用时运行初始化代码。

我们从这得到了什么?

  • 我们 100% 确定值不会改变。

  • 更好的资源管理。有时,你需要初始化多个属性,其中一些可能需要很长时间。这是一个考虑使用 lazy 并推迟一些工作以改善启动时间和内存分配的地方。

看看这个例子:

go 复制代码
class Example {
    val lazyExample by lazy { "Example" }

    fun useExample() {
        println(lazyExample)
    }
}

fun main() {
    val example = Example()
    // 现在 `lazyExample` 属性尚未初始化
    example.useExample()
    // 现在 `lazyExample` 有了值,因为在 `useExample()` 函数中使用了它
}

fun main() {
    val example = Example()
    // 外部读取属性也会初始化它
    println(example.lazyExample)
}

如示例所示,大多数时候,你会希望你的 lazy 是一个 lambda 函数,这是一种直观的属性初始化方式。

实际上,lazy 只是 Kotlin 委托的智能使用。默认情况下,它使用初始化器的线程安全版本。

简而言之,下面的代码有一个在开始时未初始化的内部 var,并且在每次 get() 时都会进行检查:

go 复制代码
// Kotlin 标准库代码
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

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

    override val value: T
        get() {
            val _v1 = _value
            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
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

lateinit

lateinit 只能用于 var。你可能希望属性在运行时改变,并在启动时将其留为未初始化状态。

它的工作方式与 lazy 不同。在这里,你不能 100% 确定属性是否已初始化。使用 lateinit 就像是告诉编译器你知道你在做什么,并且属性将在首次读取属性之前被初始化。否则,它将崩溃。

我们从这得到了什么?

  • 虽然 lazy 只能在 lambda 中初始化;lateinit 初始化可以发生在程序的任何地方,这是瓶颈优化的好地方。

  • 可变性是你想要的。

看看它是如何工作的:

go 复制代码
class Example {
    lateinit var lazyExample: String

    fun initializeExample() {
        lazyExample = "example"
    }

    fun useExample() {
        println(lazyExample)
    }
}

// 崩溃示例
fun main() {
    val example = Example()

    example.useExample() // CRASH: UninitializedPropertyAccessException
}

// 工作示例
fun main() {
    val example = Example()

    example.initializeExample() // 首先需要初始化变量

    example.useExample() // 打印 example
}

实际上,你可以检查变量是否已初始化,并且只在初始化代码时运行:

go 复制代码
class Example {
    lateinit var lazyExample: String

    fun initializeExample() {
        lazyExample = "example"
    }

    fun useExample() {
        // 在真实应用中不要这样做
        if (::lazyExample.isInitialized) {
            println(lazyExample)
        } else {
            initializeExample()
            println(lazyExample)
        }
    }
}

然而,你绝不应该在你的程序中使用它!isInitialized 应该仅用于测试,因为它使用反射并且非常难以阅读。如果你需要在运行时检查变量是否已初始化,使用可空版本:

go 复制代码
class Example {
    var lazyExample: String? = null

    fun initializeExample() {
        lazyExample = "example"
    }

    fun useExample() {
        if (lazyExample != null) {
            println(lazyExample)
        } else {
            initializeExample()
            println(lazyExample)
        }
    }
}

原始类型

通常,使用原始类型时不可能使用 lateinit,因为 Kotlin 创建了一个存储属性的后端字段。因此,你不能使用原始类型。

相反,你应该使用 Delegates.notNull<T>()。它的工作方式相同。唯一改变的是签名:

go 复制代码
class Example {
    // 用于原始类型
    var lazyExample by Delegates.notNull<Int>()

    fun initializeExample() {
        lazyExample = 1
    }

    fun useExample() {
        println(lazyExample)
    }
}

实际上,它也被装箱,但通过委托完成。

感谢阅读!如果你学到了新东西,请关注我获取更多!

相关推荐
xcl09255 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐6 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长7 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn7 小时前
Spring 注解使用详解
java·spring
其实防守也摸鱼7 小时前
Fastjson 反序列化漏洞(JNDI注入)
android·数据库·安全·oracle·自动化·fastjson·反序列化
一条小小yu7 小时前
Spring IoC的理解
java·后端·spring
落魄实习生7 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈7 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
茉莉玫瑰花茶8 小时前
GO [ 方法 ]
开发语言·后端·golang
狗凯之家源码网8 小时前
微博图床 PHP 系统搭建与实战应用指南
android·开发语言·php