Skip to content

Lab: Kotlin 委托属性运行验证 ​

对应知识库文档:Kotlin 委托属性(Delegated Properties)

实验目标 ​

用一段纯 Kotlin/JVM 程序验证「委托属性」的五个关键行为,把文档里的机制落成可观察的输出:

  1. lazy 只在首次访问求值并缓存,后续访问复用。
  2. observable 在每次设值后回调 onChange,且不改变被写的值。
  3. vetoable 在设值前回调,返回 false 可静默拒绝非法赋值。
  4. map 委托把 Map 字典直接映射成对象字段。
  5. 自定义 ReadWriteProperty 委托:在 setValue 里统一把字符串转大写。

这些例子分别在说明什么 ​

  • lazy
    • 作用:把“首次真正用到时才初始化,且结果只算一次”封装成统一语义。
    • 特点:第一次访问才执行 lambda,之后直接返回缓存;默认线程安全模式是 SYNCHRONIZED。
    • 典型用例:昂贵对象延迟创建、页面首次需要时再初始化 repository / parser / 配置解析结果。
  • observable
    • 作用:把“字段变更通知”挂在属性本身上。
    • 特点:赋值完成后回调,能拿到 property / old / new;它只通知,不拦截、不改写值。
    • 典型用例:状态变更日志、埋点、调试某字段为什么被频繁写入。
  • vetoable
    • 作用:在写入前做守门校验,避免属性进入非法状态。
    • 特点:回调返回 true 才真正写入;返回 false 时静默保持旧值,不抛异常。
    • 典型用例:分数范围、分页参数、重试次数、状态机合法流转。
  • map
    • 作用:把“从字典里按 key 取字段”的样板代码压缩成属性访问。
    • 特点:属性名会映射到 Map 中同名 key;更适合轻量配置映射,不适合复杂强类型领域模型。
    • 典型用例:配置表、轻量 JSON、arguments / extras、动态字段集合。
  • 自定义 ReadWriteProperty
    • 作用:把通用读写规则抽成可复用委托组件。
    • 特点:属性外观不变,但 getValue / setValue 可统一做改写、校验、持久化或日志。
    • 典型用例:统一 trim、大小写归一、脱敏、SharedPreferences / DataStore 代理。

工程要点 ​

  • 委托属性是 Kotlin 标准库能力,零额外依赖,可直接在 labs/kotlin/host 运行。
  • 关键认知:by 编译后属性无 backing field,读写被转发到委托的 getValue / setValue。
  • 它解决的不是“少写几个字”,而是把属性管理策略(初始化、校验、通知、映射、改写)从业务类里抽离出来。

关键实现速览 ​

kotlin
// 验证 vetoable 拒绝越界赋值(静默保持旧值)
var score by Delegates.vetoable(0) { _, _, new -> new in 0..100 }
score = 80
score = 150   // 被否决,score 仍为 80

// 自定义委托:写入即转大写
class UpperCaseDelegate : ReadWriteProperty<Any?, String> {
    private var value = ""
    override fun getValue(thisRef: Any?, property: KProperty<*>) = value
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        this.value = value.uppercase()
    }
}
var name by UpperCaseDelegate()   // name = "ada" -> 读出 "ADA"
  • 为什么看这段:vetoable 的「拒绝=静默保持旧值」和自定义委托的「setValue 里改写」是最容易误判的两点,亲眼跑一次比读定义更稳。
  • 完整源码:DelegatedPropertiesDemo.kt

运行方式 ​

在 labs/kotlin/host 根目录执行(host 已注册 runDelegatedProperties task):

bash
./gradlew runDelegatedProperties

预期现象 ​

text
=== 1. lazy 只算一次 ===
  lazy lambda run
  first  : computed once
  second : computed once

=== 2. observable 设值后通知 ===
  observed changed: <init> -> A
  observed changed: A -> B

=== 3. vetoable 拒绝越界赋值 ===
  after 80 : 80
  after 150 (rejected): 80

=== 4. map 后端委托 ===
  host=api.example.com timeout=30

=== 5. 自定义委托:写入转大写 ===
  name after assign 'ada' -> ADA
  • 观察重点:第 1 节 lazy lambda run 只打印一次,证明缓存生效;第 3 节 150 被拒后 score 仍为 80,证明 vetoable 是静默拒绝。

常见误区 / 误配 ​

  • 以为 lazy 每次读都重算 → 实际只算一次并缓存。
  • 在 Compose 里用 by 期望随重组刷新 → by 缓存语义与重组无关,应用 remember / derivedStateOf。
  • 误以为 observable 能阻止非法值写入 → 它只能在写入后通知,拦截要用 vetoable。
  • 误以为 vetoable 拒绝时会抛异常 → 实际静默保持旧值,调用方需读回确认。
  • 误把 map 委托当成强类型模型替代品 → 它适合轻量映射,不适合复杂领域对象演进。

对应文档与关键源码链接 ​

站点构建时间:2026/8/24 23:43:17