跳到主要内容

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 lazylateinit 的区别?

答案

特性by lazylateinit
适用类型val(只读)var(可变)
初始化时机首次访问时手动赋值
null 安全✅ 不可为 null✅ 不可为 null
基本类型✅ 支持❌ 不支持
线程安全默认线程安全不保证
检查已初始化不需要::prop.isInitialized
典型场景昂贵计算、单例DI 注入、View 绑定

相关链接