Kotlin 委托机制
问题
Kotlin 的委托(Delegation)机制是什么?属性委托有哪些应用场景?
答案
1. 类委托
Kotlin 原生支持委托模式,使用 by 关键字将接口实现委托给另一个对象:
interface Logger {
fun log(message: String)
}
class ConsoleLogger : Logger {
override fun log(message: String) = println("[LOG] $message")
}
// 类委托 —— 将 Logger 的实现委托给 consoleLogger
class UserService(logger: Logger) : Logger by logger {
fun createUser(name: String) {
// 直接调用 log,实际由 consoleLogger 执行
log("Creating user: $name")
}
}
val service = UserService(ConsoleLogger())
service.log("test") // [LOG] test
service.createUser("Alice") // [LOG] Creating user: Alice
编译后等价于手动转发所有方法调用,但代码量大幅减少。组合优于继承的最佳实践。
2. 属性委托
属性委托将属性的 get/set 操作委托给一个特殊对象:
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "Delegated value for '${property.name}'"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("Setting '${property.name}' to '$value'")
}
}
class Example {
var prop: String by Delegate()
}
3. 标准库内置委托
lazy — 懒初始化
// 首次访问时才计算,之后缓存结果
val heavyData: List<Data> by lazy {
println("Computing...")
loadFromDatabase()
}
// lazy 有三种线程安全模式
val a by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { ... } // 默认,线程安全
val b by lazy(LazyThreadSafetyMode.PUBLICATION) { ... } // 允许多线程计算,取第一个结果
val c by lazy(LazyThreadSafetyMode.NONE) { ... } // 无同步,性能最高
Android 中的 lazy 最佳实践
Activity/Fragment中使用lazy延迟初始化 View 或 ViewModel- 单线程场景用
LazyThreadSafetyMode.NONE避免同步开销 - 注意:
lazy的值一旦初始化不可更改,如需可变用Delegates.observable
observable — 可观察属性
var name: String by Delegates.observable("initial") { property, oldValue, newValue ->
println("${property.name}: $oldValue → $newValue")
}
name = "Alice" // name: initial → Alice
name = "Bob" // name: Alice → Bob
vetoable — 可否决属性
var age: Int by Delegates.vetoable(0) { _, _, newValue ->
newValue >= 0 // 返回 false 时拒绝赋值
}
age = 25 // 成功
age = -1 // 被拒绝,age 仍为 25
Map 委托 — 属性从 Map 中取值
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
val user = User(mapOf("name" to "Alice", "age" to 25))
println(user.name) // Alice
println(user.age) // 25
Map 委托的应用场景
JSON 解析、Bundle 参数提取、配置读取等——属性名对应 key 值。在 Android 中常用于 Fragment Arguments 的解析。
4. 自定义属性委托
// SharedPreferences 委托 —— Android 常见用法
class PreferenceDelegate<T>(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: T
) : ReadWriteProperty<Any?, T> {
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (defaultValue) {
is String -> prefs.getString(key, defaultValue) as T
is Int -> prefs.getInt(key, defaultValue) as T
is Boolean -> prefs.getBoolean(key, defaultValue) as T
is Long -> prefs.getLong(key, defaultValue) as T
else -> throw IllegalArgumentException("Unsupported type")
}
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
prefs.edit().apply {
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
is Long -> putLong(key, value)
}
apply()
}
}
}
// 使用
class Settings(prefs: SharedPreferences) {
var username: String by PreferenceDelegate(prefs, "username", "")
var darkMode: Boolean by PreferenceDelegate(prefs, "dark_mode", false)
}
val settings = Settings(prefs)
settings.username = "Alice" // 自动写入 SP
println(settings.username) // 自动从 SP 读取
5. Fragment/Activity Arguments 委托
// Fragment 参数委托
fun <T> Fragment.argument(key: String): ReadOnlyProperty<Fragment, T> =
ReadOnlyProperty { thisRef, _ ->
@Suppress("UNCHECKED_CAST")
thisRef.requireArguments().get(key) as T
}
class UserFragment : Fragment() {
private val userId: String by argument("user_id")
private val userName: String by argument("user_name")
}
常见面试问题
Q1: by lazy 的实现原理?
答案:
lazy 返回一个 Lazy<T> 对象,默认使用双重检查锁(DCL)实现线程安全:
// 简化版实现
private class SynchronizedLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED
override val value: T
get() {
if (_value === UNINITIALIZED) {
synchronized(this) {
if (_value === UNINITIALIZED) {
_value = initializer!!()
initializer = null // 释放 initializer 引用
}
}
}
@Suppress("UNCHECKED_CAST")
return _value as T
}
}
Q2: 类委托和装饰器模式的关系?
答案:
类委托是实现装饰器模式的语法糖。可以委托大部分方法,只覆盖需要增强的方法:
class LoggingList<T>(
private val inner: MutableList<T>
) : MutableList<T> by inner { // 所有方法委托给 inner
// 只覆盖需要增强的方法
override fun add(element: T): Boolean {
println("Adding: $element")
return inner.add(element)
}
}
Q3: by viewModels() 是怎么工作的?
答案:
by viewModels() 是 Android KTX 提供的属性委托,底层利用 lazy + ViewModelProvider:
// 简化原理
inline fun <reified VM : ViewModel> Fragment.viewModels(): Lazy<VM> {
return lazy {
ViewModelProvider(this)[VM::class.java]
}
}
// 使用
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
}
首次访问 viewModel 时才创建,之后缓存。ViewModel 的生命周期由 ViewModelStore 管理。
Q4: by lazy 和 lateinit 的区别?
答案:
| 特性 | by lazy | lateinit |
|---|---|---|
| 适用类型 | val(只读) | var(可变) |
| 初始化时机 | 首次访问时 | 手动赋值 |
| null 安全 | ✅ 不可为 null | ✅ 不可为 null |
| 基本类型 | ✅ 支持 | ❌ 不支持 |
| 线程安全 | 默认线程安全 | 不保证 |
| 检查已初始化 | 不需要 | ::prop.isInitialized |
| 典型场景 | 昂贵计算、单例 | DI 注入、View 绑定 |