彻底讲清楚 Kotlin 的 by 关键字 和 lazy 委托

一、一句话理解核心概念

by 是 Kotlin 的“委托语法”:它让你把某个属性的 getter/setter 逻辑“外包”给另一个对象。
lazyby 最常用的搭档:它实现“首次访问时才初始化”的延迟加载。

你可以把 by lazy 想象成:

“这个变量我先占个位,等你真要用的时候,我再花力气去创建它。”


二、先看 by lazy:延迟初始化神器

✅ 基础语法

val database: Database by lazy {
    Database.getInstance(context) // 只在第一次访问时执行
}

✅ 特点

特性说明
线程安全默认使用 LazyThreadSafetyMode.SYNCHRONIZED(多线程安全)
只初始化一次第一次访问后,后续直接返回缓存值
懒加载不访问就不创建,节省内存和启动时间

📱 Android 实战场景

场景1:单例数据库(避免 Application 中过早初始化)
class MyRepository {
    private val db: AppDatabase by lazy {
        Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "my-db"
        ).build()
    }

    fun getUser(id: Int) = db.userDao().findById(id)
}

✅ 好处:App 启动更快,只有真正用到数据库时才初始化。

场景2:Fragment 中的 ViewModel(避免重复获取)
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by lazy {
        ViewModelProvider(this)[UserViewModel::class.java]
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // 第一次访问 viewModel 时才创建
        observeData(viewModel.uiState)
    }
}

💡 虽然官方推荐 by viewModels()(见下文),但理解 lazy 有助于理解原理。

场景3:昂贵对象(如图像处理器、加密器)
private val imageCompressor: ImageCompressor by lazy {
    ImageCompressor(maxSize = 1024) // 初始化耗时 50ms
}

fun uploadImage(bitmap: Bitmap) {
    val compressed = imageCompressor.compress(bitmap) // 首次调用才创建
    api.upload(compressed)
}

三、by 的本质:属性委托(Property Delegation)

by lazy { ... } 只是 by 的一种用法。
by 的通用形式是:

val/var <property> by <delegate>

Kotlin 会把属性的 读(get)写(set) 操作委托给 <delegate> 对象。

🔧 自定义委托示例(理解原理)

class NotNullString {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "Hello from delegate!"
    }
}

class MyClass {
    val greeting: String by NotNullString()
}

// 使用
println(MyClass().greeting) // 输出: Hello from delegate!

getValue / setValue 是委托协议的核心方法。


四、Kotlin 内置的常用委托(除了 lazy

委托用途Android 场景
by lazy { }延迟初始化(只读)单例、昂贵对象
by mutableStateOf()Compose 状态var count by mutableStateOf(0)
by viewModels()获取 ViewModelFragment/Activity 中标准写法
by preferences()封装 SharedPreferences读写配置项
by Delegates.observable()监听属性变化自动更新 UI

📌 重点:by viewModels() vs by lazy

很多开发者混淆这两者,其实:

// ❌ 不推荐(虽然能用)
private val viewModel: MyViewModel by lazy {
    ViewModelProvider(this)[MyViewModel::class.java]
}

// ✅ 官方推荐(Jetpack 写法)
private val viewModel: MyViewModel by viewModels()

为什么 viewModels() 更好?

  • 自动处理 作用域(Activity/Fragment 生命周期)
  • 支持 ViewModelFactory(依赖注入)
  • SavedStateHandle 无缝集成
  • 代码更简洁、语义更清晰

💡 viewModels() 本身也是基于 by 委托实现的!


五、lazy 的三种线程安全模式

val a by lazy { ... } 
// 等价于
val a by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ... }

// 其他选项:
val b by lazy(LazyThreadSafetyMode.PUBLICATION) { ... } // 允许多次初始化,但最终一致
val c by lazy(LazyThreadSafetyMode.NONE) { ... }        // 非线程安全,性能最高

Android 开发建议:默认用 SYNCHRONIZED(安全第一),除非确定单线程且追求极致性能。


六、常见误区 & 最佳实践

❌ 误区1:在 lazy 中使用可能为 null 的上下文

class MyFragment : Fragment() {
    // ❌ 危险!context 在 Fragment 未 attach 时为 null
    private val helper by lazy { Helper(context!!) }
}

✅ 正确做法:

private val helper by lazy { Helper(requireContext()) }

❌ 误区2:用 lazy 初始化需要生命周期感知的对象

// ❌ 不要这样用 lazy 初始化需要 Context 的东西(如果 Context 可能变化)
private val toast by lazy { Toast.makeText(context, "Hi", Toast.LENGTH_SHORT) }

✅ 更好的方式:用函数封装

private fun showToast() = Toast.makeText(requireContext(), "Hi", Toast.LENGTH_SHORT).show()

✅ 最佳实践1:只对 初始化昂贵可能不用 的对象使用 lazy

  • 数据库、网络客户端、大对象
  • 不要用在 IntString 等轻量对象上

✅ 最佳实践2:优先使用框架提供的委托(如 viewModels()

  • 它们更安全、更符合 Android 生命周期

七、总结一句话

by 是 Kotlin 的委托机制,lazy 是它最经典的实现 —— 通过“按需初始化”提升性能,配合 Android 生命周期感知委托(如 viewModels()),能写出既高效又安全的代码。

记住:

  • by lazy → 用于昂贵、懒加载的单例对象
  • by viewModels() → 用于获取 ViewModel(别自己用 lazy 实现!)
  • by 本身 → 是 Kotlin 强大元编程能力的体现,可扩展性极强

💡 小测试:下面哪种写法更好?

// A
private val db = Room.databaseBuilder(...).build()

// B
private val db by lazy { Room.databaseBuilder(...).build() }

✅ 答案:B
因为数据库初始化耗时,且可能某些用户路径根本用不到,用 lazy 能加速 App 启动。

Kotlin 中,`by` 关键字用于实现 **委托模式(Delegation Pattern)**,它允许一个对象将其某些职责委托给另一个对象。Kotlin委托模式提供了语言级别的支持,使得实现委托变得更加简单直观。 ### 1. 属性委托(Property Delegation) Kotlin 中最常见的是属性委托,使用 `by` 关键字将属性的 `get()` `set()` 委托给一个实现了 `ReadWriteProperty` 或 `ReadOnlyProperty` 接口的对象。 ```kotlin class Example { var prop: String by Delegate() } class Delegate { operator fun getValue(example: Any?, property: KProperty<*>): String { println("Getting value of ${property.name}") return "Value" } operator fun setValue(example: Any?, property: KProperty<*>, value: String) { println("Setting value of ${property.name} to $value") } } fun main() { val example = Example() example.prop = "Hello" // 调用 setValue println(example.prop) // 调用 getValue } ``` ### 2. 类委托(Class Delegation) Kotlin 允许你将一个类的方法实现委托给另一个类,这通过 `by` 在类声明中实现接口时使用。 ```kotlin interface A { fun doSomething() } class AImpl : A { override fun doSomething() { println("AImpl does something") } } class B(val a: A) : A by a // 将 A 的实现委托给 a fun main() { val b = B(AImpl()) b.doSomething() // 实际调用的是 AImpl 的 doSomething() } ``` ### 3. 常见的内置委托 Kotlin 标准库提供了一些常用的委托实现: - **`lazy`**:延迟初始化属性 - **`observable`**:属性变化时通知 - **`vetoable`**:允许在属性变化时进行验证 ```kotlin class User { var name: String by Delegates.observable("default") { prop, old, new -> println("Property ${prop.name} changed from $old to $new") } } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuwu_q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值