一、核心概念
属性委托(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("执行初始化")
"加载数据"
}
三种模式:
-
LazyThreadSafetyMode.SYNCHRONIZED默认,加锁,多线程安全 -
PUBLICATION不加锁,多个线程可同时执行lambda,先算出的值生效 -
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)
回调返回 Boolean:true 接受新值,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
✅ 区分重点(面试高频坑)
-
val x by Delegate()→ 属性委托,代理 get/set -
class A : Interface by impl→ 类委托(接口委托),代理接口全部方法
五、常见实战场景
-
懒加载:
by lazy(ViewModel、大对象初始化) - 属性变更监听:observable(UI状态自动刷新,Compose 部分场景替代)
- 参数校验:vetoable(表单字段校验)
- 配置/JSON解析:Map委托
- SharedPreferences 封装:自定义委托一键读写sp
- Android ViewModel 委托、SavedState 委托
六、常见坑 & 面试考点
-
lazy只支持val,var不能用; -
lazy线程模式选错会出现多线程重复初始化; - observable 回调是赋值完成之后,无法阻止赋值;需要拦截用 vetoable;
- 委托对象是只创建一次:
val a by LazyDelegate()委托对象实例仅初始化一次; - 委托属性无法直接访问幕后字段
field;读写全部交给委托类; - 顶层委托属性
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 逻辑代理到外部对象。
要求委托对象提供 operatorgetValue(val)、setValue(var)函数。
标准库常用委托:
-
lazy:延迟初始化val; - observable:监听属性变更;
- vetoable:条件拦截赋值;
- Map映射委托快速解析键值数据。
注意区分【属性委托 by】和【接口类委托 by】,二者语法相同用途完全不同。