Kotlin 自定义属性

85 阅读1分钟

在 Kotlin 中,自定义属性委托允许开发者将属性的读写逻辑封装到独立对象中,实现代码复用和解耦。以下是实现自定义委托的完整指南:


一、自定义委托的实现方式

1. ‌通过 operator 方法实现

  • 只读属性(val ‌:实现 getValue 方法。

    class StringDelegate {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
            return "固定值或动态逻辑"
        }
    }
    
  • 可变属性(var ‌:需同时实现 getValuesetValue

    class LoggingDelegate<T>(private var value: T) {
        operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
            println("读取属性 ${property.name}$value")
            return value
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
            println("设置属性 ${property.name}: $value$newValue")
            value = newValue
        }
    }
    

2. ‌通过接口实现

  • 只读属性‌:实现 ReadOnlyProperty 接口。

    class ReadOnlyDelegate : ReadOnlyProperty<Any, String> {
        override fun getValue(thisRef: Any, property: KProperty<*>): String {
            return "接口实现返回值"
        }
    }
    
  • 可变属性‌:实现 ReadWriteProperty 接口。

    class ReadWriteDelegate : ReadWriteProperty<Any, Int> {
        private var _value = 0
        override fun getValue(thisRef: Any, property: KProperty<*>): Int = _value
        override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
            _value = value
        }
    }
    

二、使用自定义委托

1. ‌基本语法

class Example {
    val readOnlyProp: String by StringDelegate()       // 只读委托
    var mutableProp: Int by ReadWriteDelegate()        // 可变委托
    var loggedProp: String by LoggingDelegate("初始值") // 带日志的委托
}

2. ‌实际应用示例

场景:属性校验委托

class RangeValidator(private var min: Int, private var max: Int) {
    private var _value = min

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = _value

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        if (value in min..max) {
            _value = value
        } else {
            throw IllegalArgumentException("${property.name} 超出范围 [$min, $max]")
        }
    }
}

class Config {
    var percentage: Int by RangeValidator(0, 100)  // 限定百分比范围
}

// 使用
val config = Config()
config.percentage = 50  // 正常
config.percentage = 120 // 抛出异常

三、核心注意事项

要点说明
方法签名匹配getValue/setValue 参数类型必须严格遵循标准(thisRef: Any?, property: KProperty<*>)58。
可见性要求若委托类与宿主类不在同一模块,需确保方法可见性为 public38。
性能优化高频访问属性时,避免在委托中执行复杂逻辑,以减少额外开销58。
接口 vs 方法接口实现更规范,operator 方法更灵活(如支持泛型参数)35。

四、典型应用场景

  1. 数据校验
    如范围检查、格式验证(邮箱、URL 等)5。
  2. 日志记录
    拦截属性读写操作,生成审计日志8。
  3. 数据绑定
    实现属性与 UI 控件、数据库字段的动态同步35。
  4. 缓存管理
    延迟加载资源或缓存计算结果(类似 lazy 的高级扩展)8。