委托属性
委托属性与标准委托
概述
Kotlin 委托属性(Delegated Properties)将属性的 getter 和 setter 委托给另一个对象处理,是 Kotlin 最强大的特性之一。标准库提供了 lazy、observable、vetoable 等内置委托,开发者也可以自定义委托实现属性监听、缓存和映射等功能。
基础概念
委托属性语法
// 基本语法:by 关键字将属性委托给委托对象
class Example {
val lazyValue: String by lazy { "延迟初始化的值" }
var observed: String by Delegates.observable("") { _, old, new ->
println("值变化: $old -> $new")
}
}
委托属性接口
// 只读属性委托接口
interface ReadOnlyProperty<in T, out V> {
operator fun getValue(thisRef: T, property: KProperty<*>): V
}
// 可变属性委托接口
interface ReadWriteProperty<in T, out V> {
operator fun getValue(thisRef: T, property: KProperty<*>): V
operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
快速上手
标准委托
import kotlin.properties.Delegates
class User {
// lazy — 延迟初始化,首次访问时计算
val config: Config by lazy { loadConfig() }
// observable — 值变化时收到通知
var name: String by Delegates.observable("") { _, old, new ->
println("姓名变化: $old -> $new")
}
// vetoable — 值变化前可以否决
var age: Int by Delegates.vetoable(0) { _, _, new ->
new >= 0 // 只有非负值才接受
}
// notNull — 延迟赋值,使用前必须初始化
var id: String by Delegates.notNull()
}
Map 委托
// 将 Map 的键映射为属性,适合解析 JSON 等场景
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
// 可变版本
class MutableUser(map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
// 使用
val user = User(mapOf("name" to "张三", "age" to 25))
println(user.name) // 张三
val mutableUser = MutableUser(mutableMapOf("name" to "张三", "age" to 25))
mutableUser.age = 26 // 自动更新 Map
详细用法
自定义委托
// 自定义委托:属性变化时持久化到 SharedPreferences
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("不支持的类型")
}
}
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)
else -> throw IllegalArgumentException("不支持的类型")
}
}.apply()
}
}
// 使用
class Settings(prefs: SharedPreferences) {
var username: String by PreferenceDelegate(prefs, "username", "")
var darkMode: Boolean by PreferenceDelegate(prefs, "dark_mode", false)
var fontSize: Int by PreferenceDelegate(prefs, "font_size", 14)
}
提供委托(provideDelegate)
// provideDelegate 在属性创建时(而非读写时)执行逻辑
class ResourceDelegate<T>(val id: Int) : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
// 根据属性名和 id 加载资源
TODO()
}
}
class ResourceLoader {
// provideDelegate 在属性初始化时调用,可用于验证
operator fun <T> provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ReadOnlyProperty<Any?, T> {
// 在属性创建时进行验证
checkProperty(property.name)
return ResourceDelegate(property.name.hashCode())
}
private fun checkProperty(name: String) {
println("属性 $name 已注册")
}
}
常见场景
Android ViewBinding 委托
// 使用委托简化 Activity 中的 ViewBinding
fun <T : ViewBinding> Activity.viewBinding(
factory: (LayoutInflater) -> T
) = object : ReadOnlyProperty<Activity, T> {
private var binding: T? = null
override fun getValue(thisRef: Activity, property: KProperty<*>): T {
return binding ?: factory(layoutInflater).also {
setContentView(it.root)
binding = it
}
}
}
// 使用
class MainActivity : AppCompatActivity() {
private val binding by viewBinding(ActivityMainBinding::inflate)
}
响应式属性委托
// 属性变化时自动通知观察者
class ObservableProperty<T>(
initialValue: T,
private val onChange: (T) -> Unit
) : ReadWriteProperty<Any?, T> {
private var value = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val old = this.value
if (old != value) {
this.value = value
onChange(value)
}
}
}
// 使用
class FormViewModel {
var email: String by ObservableProperty("") { newEmail ->
validateEmail(newEmail)
}
}
注意事项
- lazy 默认是线程安全的(LazyThreadSafetyMode.SYNCHRONIZED),可切换为 PUBLICATION 或 NONE
- Delegates.notNull() 在读取前必须赋值,否则抛出 IllegalStateException
- Map 委托的键名必须与属性名完全匹配
- 自定义委托时注意处理泛型擦除问题
- 委托属性不能是局部变量(除了 delegated local property with lazy)
- 委托对象的 getValue 和 setValue 是运算符重载,使用 operator 修饰
进阶用法
属性监听链
// 组合多个委托实现属性监听链
fun <T> Delegates.observableWithVeto(
initialValue: T,
veto: (old: T, new: T) -> Boolean = { _, _ -> true },
onChange: (old: T, new: T) -> Unit = { _, _ -> }
) = object : ReadWriteProperty<Any?, T> {
private var value = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): T = value
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val old = this.value
if (old != value && veto(old, value)) {
this.value = value
onChange(old, value)
}
}
}
// 使用
var score: Int by observableWithVeto(
initialValue = 0,
veto = { _, new -> new in 0..100 }, // 只接受 0-100
onChange = { old, new -> println("分数: $old -> $new") }
)