一、一句话理解核心概念
by是 Kotlin 的“委托语法”:它让你把某个属性的 getter/setter 逻辑“外包”给另一个对象。
lazy是by最常用的搭档:它实现“首次访问时才初始化”的延迟加载。
你可以把 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() | 获取 ViewModel | Fragment/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
- 数据库、网络客户端、大对象
- 不要用在
Int、String等轻量对象上
✅ 最佳实践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 启动。
623

被折叠的 条评论
为什么被折叠?



