跳转到内容

Kotlin委托属性

来自代码酷

Kotlin委托属性[编辑 | 编辑源代码]

Kotlin委托属性(Delegated Properties)是一种强大的语言特性,允许开发者将属性的访问逻辑(getter/setter)委托给另一个对象处理。通过这种方式,可以复用属性逻辑,减少样板代码,并实现更灵活的属性行为。

介绍[编辑 | 编辑源代码]

Kotlin中的属性通常由字段和访问器(getter/setter)组成。委托属性通过`by`关键字将属性的读写操作委托给一个实现了特定接口的对象(称为委托对象)。Kotlin标准库提供了几种内置委托(如`lazy`、`observable`),同时也支持自定义委托。

委托属性的核心接口是:

  • `ReadOnlyProperty<in R, out T>`:用于只读属性(`val`)
  • `ReadWriteProperty<in R, T>`:用于可变属性(`var`)

基本语法[编辑 | 编辑源代码]

定义一个委托属性的基本语法如下:

class Example {
    var delegatedProperty: String by Delegate()
}

其中`Delegate`是一个实现了`ReadWriteProperty<Any?, String>`接口的类。

内置委托示例[编辑 | 编辑源代码]

Kotlin标准库提供了几种常用的委托:

lazy 委托[编辑 | 编辑源代码]

`lazy`用于延迟初始化属性,仅在第一次访问时计算值:

val lazyValue: String by lazy {
    println("Computed!")
    "Hello"
}

fun main() {
    println(lazyValue) // 输出: Computed! \n Hello
    println(lazyValue) // 仅输出: Hello(值已缓存)
}

observable 委托[编辑 | 编辑源代码]

`observable`允许在属性值改变时收到通知:

var observedValue: String by Delegates.observable("<初始值>") { 
    property, oldValue, newValue ->
    println("${property.name}$oldValue 变为 $newValue")
}

fun main() {
    observedValue = "第一次修改" // 输出: observedValue 从 <初始值> 变为 第一次修改
    observedValue = "第二次修改" // 输出: observedValue 从 第一次修改 变为 第二次修改
}

自定义委托[编辑 | 编辑源代码]

要创建自定义委托,需要实现`ReadWriteProperty`接口:

class RangeRegulator(
    initialValue: Int,
    private val minValue: Int,
    private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
    private var fieldData = initialValue

    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return fieldData
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in minValue..maxValue) {
            fieldData = value
        } else {
            throw IllegalArgumentException("值必须在 $minValue 和 $maxValue 之间")
        }
    }
}

class Player {
    var level: Int by RangeRegulator(1, 1, 100)
}

fun main() {
    val player = Player()
    player.level = 50 // 正常赋值
    println(player.level) // 输出: 50
    player.level = 150 // 抛出异常: IllegalArgumentException
}

属性委托原理[编辑 | 编辑源代码]

Kotlin编译器会为委托属性生成以下代码(以`var prop: Type by Delegate()`为例):

classDiagram class Example { -delegate: Delegate +getProp(): Type +setProp(value: Type) } class Delegate { +getValue(thisRef: Any?, property: KProperty<*>): Type +setValue(thisRef: Any?, property: KProperty<*>, value: Type) } Example --> Delegate : 委托

对应的伪Java代码:

public final class Example {
    private final Delegate delegate$delegate = new Delegate();
    
    public Type getProp() {
        return delegate$delegate.getValue(this, $$delegatedProperties[0]);
    }
    
    public void setProp(Type value) {
        delegate$delegate.setValue(this, $$delegatedProperties[0], value);
    }
}

实际应用案例[编辑 | 编辑源代码]

案例1:SharedPreferences 封装[编辑 | 编辑源代码]

Android开发中常用委托属性简化SharedPreferences访问:

class Preference<T>(
    private val context: Context,
    private val name: String,
    private val defaultValue: T,
    private val prefName: String = "default_prefs"
) : ReadWriteProperty<Any?, T> {
    private val prefs by lazy {
        context.getSharedPreferences(prefName, Context.MODE_PRIVATE)
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return when (defaultValue) {
            is String -> prefs.getString(name, defaultValue) as T
            is Int -> prefs.getInt(name, defaultValue) as T
            is Boolean -> prefs.getBoolean(name, defaultValue) as T
            else -> throw IllegalArgumentException("不支持的类型")
        }
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        with(prefs.edit()) {
            when (value) {
                is String -> putString(name, value)
                is Int -> putInt(name, value)
                is Boolean -> putBoolean(name, value)
                else -> throw IllegalArgumentException("不支持的类型")
            }.apply()
        }
    }
}

// 使用示例
class Settings(context: Context) {
    var darkModeEnabled by Preference(context, "dark_mode", false)
    var userName by Preference(context, "user_name", "Guest")
}

案例2:视图绑定[编辑 | 编辑源代码]

在Android视图绑定中避免重复的findViewById调用:

fun <T : View> Activity.viewBinding(@IdRes id: Int): Lazy<T> {
    return lazy { findViewById<T>(id) }
}

class MainActivity : AppCompatActivity() {
    private val buttonSubmit by viewBinding<Button>(R.id.btn_submit)
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        buttonSubmit.setOnClickListener { /*...*/ } // 首次访问时执行findViewById
    }
}

数学表示[编辑 | 编辑源代码]

委托属性的行为可以用以下公式表示: 对于属性`p`委托给对象`d`:

  • 读取操作:`p` ⇨ `d.getValue(thisRef, property)`
  • 写入操作:`p = v` ⇨ `d.setValue(thisRef, property, v)`

其中:

  • thisRef{接收者对象,null}
  • propertyKProperty<*>(反射属性信息)

总结[编辑 | 编辑源代码]

Kotlin委托属性的主要优势包括:

  • 代码复用:共享属性访问逻辑
  • 封装:隐藏复杂实现细节
  • 声明式语法:通过`by`关键字简洁表达意图
  • 灵活性:支持动态属性行为

通过合理使用标准库委托和自定义委托,可以显著提升代码的可维护性和表达力。