前置知识: Kotlin

委托属性

2 minIntermediate2026/6/14

委托属性与标准委托

概述

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") }
)