Kotlin 属性委托 by

一、核心概念

属性委托(Delegated Properties)
语法:val/var 属性名: 类型 by 委托对象
含义:属性的 get() / set() 逻辑不自己实现,交给另一个对象代理执行

思想:组合优于继承,把属性读写职责外包。

两个关键接口(约定规范,不是强制继承,但推荐实现)

// 只读属性 val 委托
public interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

// 可变属性 var 委托
public interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}
  • thisRef:属性所属的实例对象;若为顶层属性 = null
  • property:属性元信息(名字、类型、注解等,反射信息)
  • 必须带有 operator 关键字

二、最简自定义委托示例

1)只读属性 val

class StringDelegate : ReadOnlyProperty<Any?, String> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("访问属性:${property.name}")
        return "委托返回的值"
    }
}

// 使用
val msg: String by StringDelegate()
fun main(){
    println(msg)
}

2)可变属性 var

class IntDelegate : ReadWriteProperty<Any?, Int> {
    private var storedValue = 0
    override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return storedValue
    }
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        println("赋值 ${property.name} = $value")
        storedValue = value
    }
}

var num: Int by IntDelegate()
fun main(){
    num = 100
    println(num)
}

💡 技巧:不用显式实现接口,只要对象拥有签名匹配的 getValue/setValue operator 函数,就能被 by 委托(鸭子类型)。

三、标准库内置委托(高频面试重点)

1. lazy() 延迟初始化(val 专用)

// 第一次访问才执行 lambda,之后缓存结果,线程安全默认同步锁
val data: String by lazy {
    println("执行初始化")
    "加载数据"
}

三种模式:

  1. LazyThreadSafetyMode.SYNCHRONIZED 默认,加锁,多线程安全
  2. PUBLICATION 不加锁,多个线程可同时执行lambda,先算出的值生效
  3. NONE 无同步,仅单线程使用,性能最高

⚠️ lazy 只能用于 val,不能用于 var!

2. Delegates.observable() 监听属性变化(var)

赋值成功后回调

import kotlin.properties.Delegates

var age: Int by Delegates.observable(0) { prop, old, new ->
    println("${prop.name}:$old → $new")
}

fun main(){
    age = 18
}

3. Delegates.vetoable() 可拦截赋值(var)

回调返回 Booleantrue 接受新值,false 拒绝本次修改

var score: Int by Delegates.vetoable(0) { _, _, new ->
    new in 0..100 // 分数0~100才允许赋值
}

4. 映射委托 Map / MutableMap

非常适合解析JSON、配置类

data class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

fun main(){
    val user = User(mapOf("name" to "张三", "age" to 22))
    println(user.name)
}

MutableMap 配合 var 读写。

四、类实例委托(容易和属性委托混淆!区分开)

// 接口实现委托(不是属性委托!)
interface Base { fun print() }
class BaseImpl : Base { override fun print() = println("base") }

// by 放在父类位置:接口方法委托给对象
class Derived(b: Base) : Base by b

区分重点(面试高频坑)

  1. val x by Delegate()属性委托,代理 get/set
  2. class A : Interface by impl类委托(接口委托),代理接口全部方法

五、常见实战场景

  1. 懒加载by lazy(ViewModel、大对象初始化)
  2. 属性变更监听:observable(UI状态自动刷新,Compose 部分场景替代)
  3. 参数校验:vetoable(表单字段校验)
  4. 配置/JSON解析:Map委托
  5. SharedPreferences 封装:自定义委托一键读写sp
  6. Android ViewModel 委托、SavedState 委托

六、常见坑 & 面试考点

  1. lazy 只支持 valvar 不能用;
  2. lazy 线程模式选错会出现多线程重复初始化;
  3. observable 回调是赋值完成之后,无法阻止赋值;需要拦截用 vetoable;
  4. 委托对象是只创建一次val a by LazyDelegate() 委托对象实例仅初始化一次;
  5. 委托属性无法直接访问幕后字段 field;读写全部交给委托类;
  6. 顶层委托属性 thisRef = null,委托内部不要强转thisRef;

七、拓展:委托属性 + 泛型封装示例(SP简易封装)

class SpDelegate<T>(private val key: String, private val default: T) : ReadWriteProperty<Any?, T> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        // 从sp读取key
        return default
    }
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        // 写入sp
    }
}

// 使用
var token: String by SpDelegate("token", "")

八、面试简答背诵版

Kotlin 属性委托 by 将属性的 get/set 逻辑代理到外部对象。
要求委托对象提供 operator getValue(val)、setValue(var)函数。
标准库常用委托:

  1. lazy:延迟初始化val;
  2. observable:监听属性变更;
  3. vetoable:条件拦截赋值;
  4. Map映射委托快速解析键值数据。
    注意区分【属性委托 by】和【接口类委托 by】,二者语法相同用途完全不同。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容