Kotlin 中的 Lazy vs Lateinit 解析

Kotlin 中的 Lazy vs Lateinit 解析

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

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

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)
    }
}

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

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

相关推荐
亚林瓜子14 分钟前
SpringBoot中使用tess4j进行OCR(在macos上面开发)
java·spring boot·macos·ocr·lstm·tess4j
孤独的追光者26 分钟前
使用Qt Designer开发上位机
开发语言·python·qt
2501_9159214330 分钟前
iOS 26 CPU 使用率监控策略 多工具协同构建性能探索体系
android·ios·小程序·https·uni-app·iphone·webview
狂团商城小师妹30 分钟前
JAVA国际版同城打车源码同城服务线下结账系统源码适配PAD支持Android+IOS+H5
android·java·ios·小程序·交友
m0_7369270434 分钟前
Java面试场景题及答案总结(2025版持续更新)
java·开发语言·后端·职场和发展
游戏开发爱好者835 分钟前
iOS 应用逆向对抗手段,多工具组合实战(iOS 逆向防护/IPA 混淆/无源码加固/Ipa Guard CLI 实操)
android·ios·小程序·https·uni-app·iphone·webview
何中应38 分钟前
IDEA实用快捷键
java·ide·intellij-idea
muyouking1139 分钟前
Rust + WASM + Svelte 深度实战:内存管理、性能权衡与图像处理进阶
开发语言·rust·wasm
虚伪的空想家1 小时前
ip网段扫描机器shell脚本
android·linux·网络协议·tcp/ip·shell·脚本·network
仟濹1 小时前
「经典数字题」集合 | C/C++
c语言·开发语言·c++