编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机的原理
  • 操作系统原理
  • 网络协议原理
  • 数据库的原理
  • 序卷导读
  • 数据本质
  • 运行模型
  • 并发设计
  • 内存真相
  • 交互系统
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 技能之旅
  • 体系建设
  • 代码品质
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 性能优化
  • 数据结构导论
  • 线性结构详解
  • 树哈希结构论
  • 容器设计实战
  • 经典算法思想
  • 工程案例剖析
  • 算法题库精练
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript案例
  • JavaScript高级
  • Kotlin精通
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 毛选解读
  • 自我精进
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

    • Kotlin精通

      • Kotlin精通模块总览
      • Kotlin基础语法
      • 类和对象
      • 高阶特性入门
      • 空安全类型原理
      • data类与equals原理
      • 协程调度器原理
      • inline与reified原理
      • 委托与属性原理
        • 1. 案例引入
          • 1.1 一段反常代码
          • 1.2 顺藤摸到根因
          • 1.3 我们要回答什么
        • 2. 架构概览
          • 2.1 委托两大家族
          • 2.2 为什么这么切
        • 3. 属性委托字节码
          • 3.1 getValue约定方法
          • 3.2 编译器生成字段
          • 3.3 KProperty是什么
          • 3.4 setValue对称结构
        • 4. lazy三种模式
          • 4.1 SYNCHRONIZED默认
          • 4.2 PUBLICATION实现
          • 4.3 NONE性能最优
          • 4.4 双重检查锁真相
        • 5. 类委托字节码
          • 5.1 接口方法转发
          • 5.2 delegate字段生成
          • 5.3 override覆盖机制
          • 5.4 与Proxy动态代理
        • 6. 内建委托家族
          • 6.1 observable源码
          • 6.2 vetoable否决实现
          • 6.3 notNull保护委托
          • 6.4 Map委托取值
        • 7. 自定义属性委托
          • 7.1 ReadWriteProperty接口
          • 7.2 provideDelegate高级
          • 7.3 委托的委托
          • 7.4 Preferences示例
        • 8. 委托性能代价
          • 8.1 一次方法转发
          • 8.2 KProperty创建
          • 8.3 lazy对象额外开销
          • 8.4 JIT能否消除
        • 9. 事故案例复盘
          • 9.1 lazy_NONE死锁
          • 9.2 observable泄漏Fragment
          • 9.3 类委托绕过override
        • 10. 综合案例串讲
          • 10.1 案例真相揭晓
          • 10.2 一个lazy委托一生
          • 10.3 设计哲学回扣
          • 10.4 委托速查表
    • 库的解读

    • 专栏博客

  • iOS开发和进阶

  • Web开发和进阶

  • Linux应用开发

  • IoT智能硬件开发

  • Apps
  • Android提升进阶
  • Kotlin精通
杨充
2026-07-03
目录

委托与属性原理

# 05.委托与属性原理

# 目录介绍

  • 1. 案例引入
    • 1.1 一段反常代码
    • 1.2 顺藤摸到根因
    • 1.3 我们要回答什么
  • 2. 架构概览
    • 2.1 委托两大家族
    • 2.2 为什么这么切
  • 3. 属性委托字节码
    • 3.1 getValue约定方法
    • 3.2 编译器生成字段
    • 3.3 KProperty是什么
    • 3.4 setValue对称结构
  • 4. lazy三种模式
    • 4.1 SYNCHRONIZED默认
    • 4.2 PUBLICATION实现
    • 4.3 NONE性能最优
    • 4.4 双重检查锁真相
  • 5. 类委托字节码
    • 5.1 接口方法转发
    • 5.2 delegate字段生成
    • 5.3 override覆盖机制
    • 5.4 与Proxy动态代理
  • 6. 内建委托家族
    • 6.1 observable源码
    • 6.2 vetoable否决实现
    • 6.3 notNull保护委托
    • 6.4 Map委托取值
  • 7. 自定义属性委托
    • 7.1 ReadWriteProperty接口
    • 7.2 provideDelegate高级
    • 7.3 委托的委托
    • 7.4 Preferences示例
  • 8. 委托性能代价
    • 8.1 一次方法转发
    • 8.2 KProperty创建
    • 8.3 lazy对象额外开销
    • 8.4 JIT能否消除
  • 9. 事故案例复盘
    • 9.1 lazy_NONE死锁
    • 9.2 observable泄漏Fragment
    • 9.3 类委托绕过override
  • 10. 综合案例串讲
    • 10.1 案例真相揭晓
    • 10.2 一个lazy委托一生
    • 10.3 设计哲学回扣
    • 10.4 委托速查表

# 1. 案例引入

# 1.1 一段反常代码

以下代码是一个真实事故——某 SDK 用 by lazy(LazyThreadSafetyMode.NONE) 做全局配置的懒加载,线上偶发 NullPointerException:

// AppConfig.kt
object AppConfig {
    val instance: Config by lazy(LazyThreadSafetyMode.NONE) {   // ⚠ NONE 模式
        parseConfig()
    }
}

// 多个线程同时启动时:
Thread { AppConfig.instance.timeout }.start()
Thread { AppConfig.instance.timeout }.start()   // 偶发返回 null

上报数据:

指标 数据
Crash 上报期间 3 天(灰度 5%)
崩溃次数 214 次
崩溃概率 ~0.02%(DAU 10 万时约 20 次/天)
崩溃线程 100% 非主线程
崩溃现场 Config.timeout 抛 NPE,但 Config 类字段声明是非空 Int

最诡异的地方在于——timeout 在 Config 类里明明是 val timeout: Int = 30,怎么可能取到 null 触发 NPE?(其实是装箱后的 Integer 引用取到 null)。

# 1.2 顺藤摸到根因

表象:Config? 声明的字段返回 null?还是 parseConfig() 里的字段没初始化就被读到?

反编译 AppConfig.instance 的 getter 字节码:

public static final Config getInstance() {
    Lazy lazy = instance$delegate;                    ; 拿 Lazy 对象
    KProperty prop = $$delegatedProperties[0];        ; 拿属性元数据
    return (Config) lazy.getValue();                  ; 委托取值
}

再深入 UnsafeLazyImpl 源码(NONE 模式对应的实现):

internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
    private var initializer: (() -> T)? = initializer
    private var _value: Any? = UNINITIALIZED_VALUE
    // ⚠ 注意:_value 和 initializer 都是普通字段,无 @Volatile

    override val value: T
        get() {
            if (_value === UNINITIALIZED_VALUE) {
                _value = initializer!!()
                initializer = null
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }
}

问题定位:JMM(Java Memory Model)下,普通字段的写入不保证对其他线程立即可见:

Time  Thread A                                Thread B
 t0   读 _value == UNINITIALIZED_VALUE
 t1   调 initializer() 分配 Config 对象 obj
 t2   obj.timeout = 30(构造函数内)
 t3   _value = obj                          读 _value(可能看到 obj,也可能仍是 UNINITIALIZED)
                                             ↓
                                             此时 obj 已经写入,但 obj.timeout 的初始化
                                             可能因为**指令重排序**尚未完成
                                             读到 obj.timeout = 0 (int 默认值)
                                             或读到装箱 Integer 引用 = null

根因:NONE 模式没有 volatile、没有 synchronized、没有 happens-before 语义——多线程读到部分构造对象是完全合法的 JMM 行为。

# 1.3 我们要回答什么

看到这里,我们至少积累了 6 个疑问:

  1. by lazy 编译后到底生成了什么?多少个类、多少个字段?
  2. SYNCHRONIZED / PUBLICATION / NONE 三种模式各自的实现有什么区别?
  3. 属性委托 by 与类委托 by 是同一个关键字,语义是同一回事吗?
  4. Delegates.observable 的通知机制怎么工作?为什么容易泄漏?
  5. KProperty<*> 是什么?为什么每次 getter 调用都要传它?
  6. 类委托与 Java 动态代理 Proxy.newProxyInstance 差在哪?

带着这 6 个问题,进入正文。


# 2. 架构概览

# 2.1 委托两大家族

Kotlin `by` 关键字
├── 类委托 (Class Delegation)
│   └── class C : I by impl { ... }
│       ├── 编译器为每个接口方法生成转发桩
│       └── 编译期完成,零反射
└── 属性委托 (Property Delegation)
    └── val prop: T by delegate
        ├── 编译器生成 backing field 存储 delegate
        └── getter/setter 内调用 delegate.getValue()/setValue()

两者对比:

维度 类委托 属性委托
语法位置 class ... : Interface by expr val/var name: T by expr
委托对象职责 实现某个接口 提供 getValue/setValue
编译器生成 接口方法转发桩 getter/setter + delegate 字段
一个类可否多个 可(每个接口一个 delegate) 可(每个属性一个 delegate)
典型用途 组合替代继承(Repo by impl) by lazy / observable / Map 取值

同一个关键字承担两个语义——这是 Kotlin 语言设计的取舍。

# 2.2 为什么这么切

疑惑:为什么 Kotlin 把两种截然不同的语义都用 by 表达?

论证:

  1. 两者都是**"把行为委托给另一个对象"**——语义在直觉上一致(把动作交给别人做)
  2. 都通过编译期代码生成实现,不依赖反射——保持性能可预测
  3. 都体现"组合优于继承"的设计哲学——GoF 十大经典设计模式之一
  4. 让 API 使用者一眼看出"这不是继承,是委托"——降低阅读复杂度

深层动机:Effective Java 第 18 条明确指出"Favor composition over inheritance"(组合优于继承)。但 Java 里实现委托语法负担极重——每个接口方法都要手写转发桩(几十个方法就是几十行样板代码)。Kotlin 用 by 关键字把这个模式变成语言级支持。

结论:by 是 Kotlin 对"组合模式"的语言级支持——把 GoF 的委托模式变成一个关键字,让"组合优于继承"从口号变成能落地的语法。


# 3. 属性委托字节码

# 3.1 getValue约定方法

val name: String by delegate

约定:delegate 对象必须提供 getValue(thisRef, property): T 方法(var 还要 setValue)。这不是接口约束,是编译期约定——只要有匹配签名的 operator 函数即可。

约定签名:

// 只读属性 val
operator fun getValue(thisRef: R, property: KProperty<*>): T

// 可写属性 var(还需要)
operator fun setValue(thisRef: R, property: KProperty<*>, value: T)

关键词:

  • operator —— Kotlin 让编译器识别为"约定方法"的标记
  • thisRef: R —— 属性所在类的引用(顶层属性时为 Nothing?)
  • property: KProperty<*> —— 属性元数据(名字、返回类型、注解等)
  • T —— 属性类型

约定不是接口的价值:可以让任何对象(如 Map、java.util.Properties、自定义 DSL 对象)通过扩展函数加上 getValue 变成合法委托对象,不需要继承特定接口。

// 标准库为 Map 加的扩展函数
operator fun <V, V1 : V> Map<in String, V>.getValue(thisRef: Any?, property: KProperty<*>): V1 =
    getOrImplicitDefault(property.name) as V1

一行扩展函数,Map 就能作为委托对象——这就是"约定优于配置"的威力。

# 3.2 编译器生成字段

class Foo {
    val name: String by MyDelegate()
}

反编译(简化):

public final class Foo {
    // 委托对象字段
    private final MyDelegate name$delegate = new MyDelegate();

    // 类初始化时构造属性元数据数组(这一段在 <clinit> 里)
    static final KProperty[] $$delegatedProperties = {
        (KProperty) new PropertyReference1Impl(
            Reflection.getOrCreateKotlinClass(Foo.class),   // 所属类
            "name",                                          // 属性名
            "getName()Ljava/lang/String;"                    // 签名
        )
    };

    // 编译器生成的 getter
    public final String getName() {
        return name$delegate.getValue(this, $$delegatedProperties[0]);
    }
}

生成物清单:

生成物 数量 内存占用 说明
xxx$delegate 实例字段 每属性 1 个 4-8 字节引用 存放委托对象
$$delegatedProperties 静态数组 每类 1 个 数组本身 + 每属性 1 个 KProperty 缓存属性元数据
KProperty 静态实例 每属性 1 个 一次生成,全局共享 反射需要
getter/setter 方法 每属性 1-2 个 字节码若干 转发到 delegate

观察:没有生成新类——所有生成物都放在原类中。这与 by lazy 里的 Lazy<T> 匿名对象不是一回事(by lazy 里额外分配的是 delegate 对象,不是新类)。

# 3.3 KProperty是什么

KProperty<*> 是 kotlin.reflect 里的接口,代表属性的元信息:

public interface KProperty<out V> : KCallable<V> {
    public val name: String                    // "name"
    public val getter: Getter<V>               // getter 引用
    public val annotations: List<Annotation>   // 注解
    // ...
}

每个委托属性会被编译器"注册"一个 KProperty 实例——存在类的静态数组 $$delegatedProperties 里。

为什么每次 getter 都要传 KProperty?

考虑一个通用委托:

class Logger : ReadOnlyProperty<Any?, String> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("read ${property.name}")     // ← 需要知道属性名
        return "value"
    }
}

class Config {
    val timeout: String by Logger()
    val retries: String by Logger()
}

同一个 Logger 实例可能被多个属性委托复用——所以运行时必须传入 KProperty 告知"你现在为哪个属性工作"。这是委托机制通用性的必要代价。

编译器优化:optimizedCallableReferences 编译选项(Kotlin 1.4+ 默认)让 KProperty 生成更精简——用轻量的 PropertyReference1Impl 而非重量的反射对象。

# 3.4 setValue对称结构

var 属性的 by 委托同时生成 setter,内部调用 delegate.setValue(this, property, newValue):

class Foo {
    var name: String by DelegateVar()
}

// 反编译等价于:
public final void setName(String value) {
    this.name$delegate.setValue(this, $$delegatedProperties[0], value);
}

读写对称:val 生成 getter;var 生成 getter + setter。约定方法一一对应。

只写委托不存在——Kotlin 没有"write-only property"概念。


# 4. lazy三种模式

# 4.1 SYNCHRONIZED默认

默认模式——用 synchronized 保证多线程只初始化一次:

val name: String by lazy { compute() }    // 默认 SYNCHRONIZED

内部实现(SynchronizedLazyImpl 源码简化):

private class SynchronizedLazyImpl<out T>(
    initializer: () -> T, lock: Any? = null
) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE     // ← @Volatile
    private val lock = lock ?: this

    override val value: T
        get() {
            val v1 = _value                          // ← volatile 读
            if (v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return v1 as T                       // ← 快路径,无锁
            }
            return synchronized(lock) {
                val v2 = _value                      // ← 双重检查
                if (v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") v2 as T
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue              // ← volatile 写
                    initializer = null               // 释放 lambda 引用(可回收捕获的对象)
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
}

关键设计 4 点:

  1. @Volatile 字段 —— 保证多线程可见性(happens-before)
  2. 双重检查锁模式 —— 快路径无锁,慢路径加锁;避免每次 get 都走 monitorenter
  3. UNINITIALIZED_VALUE 哨兵 —— 让 null 也能作为合法的初始化结果(区分"未初始化"和"初始化为 null")
  4. initializer = null —— 初始化后释放 lambda 引用,让 lambda 捕获的对象可被 GC

# 4.2 PUBLICATION实现

多线程可并发初始化,但只有一个结果被"发布":

val name by lazy(LazyThreadSafetyMode.PUBLICATION) { compute() }

SafePublicationLazyImpl 源码简化:

private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
    @Volatile private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    private val final: Any = Any()          // CAS 目标标记

    override val value: T
        get() {
            val v1 = _value
            if (v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST") return v1 as T
            }
            val initializerValue = initializer
            if (initializerValue != null) {
                val newValue = initializerValue()
                if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                    initializer = null       // 只有胜利者能置空
                    return newValue
                }
            }
            @Suppress("UNCHECKED_CAST") return _value as T
        }

    companion object {
        private val valueUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SafePublicationLazyImpl::class.java, Any::class.java, "_value"
        )
    }
}

关键机制:AtomicReferenceFieldUpdater.compareAndSet 让并发的初始化竞争,胜者的值被采用,其它值被丢弃。

代价与风险:

项 说明
无锁 相比 SYNCHRONIZED 少一次 monitorenter,更快
initializer 可能被多次调用 竞争的多个线程各调一次
副作用要小心 例如 initializer 里发日志、加计数器会重复
内存开销 多一个 AtomicReferenceFieldUpdater 静态字段

典型场景:initializer 是纯函数、无副作用、多次执行结果一致——例如解析常量、加载资源。

# 4.3 NONE性能最优

单线程模式——不加锁,只用普通字段:

val name by lazy(LazyThreadSafetyMode.NONE) { compute() }

UnsafeLazyImpl 源码全文(很短):

internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    private var _value: Any? = UNINITIALIZED_VALUE        // ← 无 @Volatile

    override val value: T
        get() {
            if (_value === UNINITIALIZED_VALUE) {
                _value = initializer!!()
                initializer = null
            }
            @Suppress("UNCHECKED_CAST") return _value as T
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
}

性能优势(相比 SYNCHRONIZED):

项 NONE SYNCHRONIZED 相对性能
字段读 普通 load volatile load +10-30%
字段写 普通 store volatile store(带内存屏障) +50-100%
无内存屏障 ✅ ❌(volatile 写有 StoreLoad 屏障) 显著
无 monitorenter 快路径都无 快路径也无(双检) 差异极小

风险:如 1.1 案例所示——多线程访问未初始化的属性时,字段写入尚未 flush 到主内存,另一线程可能看到 null 甚至部分初始化对象。JMM 允许指令重排序是 NONE 模式的根本坑。

唯一合法场景:

  • 私有属性 + 确定单线程访问
  • 在 Activity.onCreate / Fragment.onViewCreated 里的 View 绑定
  • 局部作用域的 lazy(虽然极少见,但合法)

# 4.4 双重检查锁真相

为什么 SYNCHRONIZED 需要双重检查?

假设只用一层检查:

override val value: T
    get() = synchronized(lock) {           // ← 每次都要 monitorenter/exit
        if (_value === UNINITIALIZED) {
            _value = initializer!!()
        }
        _value as T
    }

问题:即使值已经初始化,每次 get 都要 monitorenter/exit——热点场景性能不可接受。JMH 实测下差 5-10 倍。

双重检查解决方案:

1. 快路径:无锁读 _value(volatile load)
2. 已初始化 → 直接返回(99.99% 走这一路)
3. 未初始化 → monitorenter
4. 慢路径:再检查 _value(可能其他线程已初始化)
5. 仍未初始化 → 调用 initializer 并存值(volatile store)

为什么 _value 必须是 @Volatile?

反例(如果 _value 不是 volatile):

Time  Thread A                                Thread B
 t0   _value == UNINITIALIZED
 t1   进入 synchronized 块
 t2   _value = new Object() { field = 42 }
 t3   离开 synchronized 块(内存屏障)      读 _value(快路径,无锁)
 t4                                          可能看到旧值 UNINITIALIZED
                                             但即使看到 obj,obj.field 可能仍是 0

volatile 提供的 happens-before:_value = xxx 之前的所有写入(包括 obj 构造函数里的字段初始化)对读到 _value = xxx 的线程都可见。

Kotlin SYNCHRONIZED 的实现就是"教科书级"双检锁范式——@Volatile + 双重检查,与 Effective Java 第 83 条推荐的 Java 版本一模一样。


# 5. 类委托字节码

# 5.1 接口方法转发

interface Repo { fun save(): Int; fun load(): Int }
class RepoImpl : Repo { override fun save() = 1; override fun load() = 2 }
class LoggingRepo(val delegate: Repo) : Repo by delegate

反编译 LoggingRepo:

public final class LoggingRepo implements Repo {
    private final Repo $$delegate_0;
    
    public LoggingRepo(Repo delegate) {
        this.$$delegate_0 = delegate;
    }
    
    // ↓ 编译器自动生成的转发桩
    public int save() { return $$delegate_0.save(); }
    public int load() { return $$delegate_0.load(); }
}

关键:编译器为每个接口方法生成一个"转发桩"——零反射、零动态开销、类型安全。

转发桩的字节码非常薄:

public int save();
    ALOAD 0
    GETFIELD LoggingRepo.$$delegate_0 : Repo
    INVOKEINTERFACE Repo.save ()I
    IRETURN

3 条指令——JIT 稳态后可以完全内联,与直接调用 delegate.save() 无异。

# 5.2 delegate字段生成

字段名规则:$$delegate_N,N 从 0 递增(支持多接口委托给多个对象):

interface A { fun a() }
interface B { fun b() }
class ImplA : A { override fun a() { } }
class ImplB : B { override fun b() { } }

class Multi(implA: A, implB: B) : A by implA, B by implB

反编译:

public final class Multi implements A, B {
    private final A $$delegate_0;      // ← 第一个委托
    private final B $$delegate_1;      // ← 第二个委托

    public Multi(A implA, B implB) {
        this.$$delegate_0 = implA;
        this.$$delegate_1 = implB;
    }

    public void a() { $$delegate_0.a(); }
    public void b() { $$delegate_1.b(); }
}

多委托的能力——Kotlin 借此实现"多重继承"的组合替代方案。这在 Java 里需要手写几十个转发方法。

# 5.3 override覆盖机制

选择性 override 接口方法即可只增强特定方法:

class LoggingRepo(val delegate: Repo) : Repo by delegate {
    override fun save(): Int {
        println("before save")
        return delegate.save()             // 或调用 $$delegate_0.save()
    }
    // load 由编译器生成的转发桩处理
}

混合行为:

方法 处理
save() 用户覆写,加日志
load() 编译器生成,直接转发

观察:用户只写关心的方法,其他默认转发——这是组合模式最简洁的表达。

优先级规则:如果同时有 override 和自动转发,override 胜出(编译器不会为已 override 的方法再生成转发桩)。

# 5.4 与Proxy动态代理

维度 Kotlin 类委托 Java Proxy.newProxyInstance
生成时机 编译期 运行时字节码生成(ProxyGenerator)
性能 直接方法调用(3 条字节码) 反射 + InvocationHandler.invoke(),约 10-100 倍慢
类型安全 编译期强类型 弱类型(Object[] args)
语法负担 一个 by 关键字 匿名内部类 + 反射 API
生成 class 数量 无新 class(转发方法内嵌) 运行时生成 $Proxy0.class
能否动态选择委托对象 ❌ 编译期固定接口 ✅ 运行时可拦截任意方法
典型用途 组合替代继承 AOP、Mock、RPC 客户端

性能实测(一次接口方法调用):

方式 耗时 (ns) 相对慢
直接调用 1-2 1x
Kotlin 类委托 2-3 1.5x
Java 反射 Method.invoke 30-60 30x
Java Proxy 40-80 40x
ByteBuddy/CGLIB 生成子类 5-10 5x

结论:类委托是编译期方案,几乎零开销;Proxy 是运行时方案,功能更灵活但性能显著更差。选择时按需权衡。


# 6. 内建委托家族

# 6.1 observable源码

var name: String by Delegates.observable("init") { prop, old, new ->
    println("$old -> $new")
}

内部实现(ObservableProperty 源码简化):

abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> {
    private var value = initialValue

    protected open fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = true
    protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit = Unit

    override fun getValue(thisRef: Any?, property: KProperty<*>): T = value

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) return
        this.value = value
        afterChange(property, oldValue, value)              // 触发监听
    }
}

Delegates.observable 的完整实现:

public inline fun <T> Delegates.observable(
    initialValue: T,
    crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
    override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) =
        onChange(property, oldValue, newValue)
}

观察:observable 用模板方法模式——ObservableProperty 抽象基类定义骨架,用户传入的 lambda 通过 afterChange 参与。这是 GoF 设计模式在标准库的典型应用。

# 6.2 vetoable否决实现

var age by Delegates.vetoable(0) { _, _, new -> new >= 0 }

age = 20      // ✅ 通过,被设置
age = -1      // ❌ 被否决,age 保持 20

beforeChange 返回 false 时不写入新值——用于拒绝无效赋值。

实现:

public inline fun <T> Delegates.vetoable(
    initialValue: T,
    crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean
): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
    override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean =
        onChange(property, oldValue, newValue)
}

observable vs vetoable:

特性 observable vetoable
覆写的方法 afterChange beforeChange
lambda 返回值 Unit Boolean(是否允许)
触发时机 写入后通知 写入前决定
适用场景 变化监听(UI 刷新) 校验拒绝(不合法值不写入)

同一个 ObservableProperty 基类衍生出两种能力——组合的美感。

# 6.3 notNull保护委托

var name: String by Delegates.notNull()
name.length      // 未赋值前访问抛异常
name = "K"       // 之后正常

用途:延迟初始化非空 var(相当于 lateinit var 的 API 版本)。

实现极其简单:

private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null
    override fun getValue(thisRef: Any?, property: KProperty<*>): T =
        value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

notNull vs lateinit var 对比:

项 Delegates.notNull lateinit var
语法 by Delegates.notNull() lateinit var
支持类型 任意非空类型(包括基本类型的装箱) 只支持非空引用类型(不能是 Int/Long/Boolean 等基本类型)
检查开销 每次 getter 判空 通过字段 null 判断 + throw UninitializedPropertyAccessException
委托对象开销 每属性一个委托对象 无额外对象
反射查询 KProperty ::name.isInitialized
推荐 只在需要基本类型装箱时用 优先选择

建议:普通对象类型用 lateinit,只在需要 Int/Long 等不能 lateinit 的场景用 Delegates.notNull()。

# 6.4 Map委托取值

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

val u = User(mapOf("name" to "K", "age" to 20))
println(u.name)     // "K"
println(u.age)      // 20

用 Map.getValue 约定从 map 中读取属性——常见于 JSON、Preferences 等场景。

标准库为 Map 加的扩展函数:

public inline operator fun <V, V1 : V> Map<in String, V>.getValue(
    thisRef: Any?, property: KProperty<*>
): V1 {
    @Suppress("UNCHECKED_CAST")
    return getOrImplicitDefault(property.name) as V1
}

// var 版本用 MutableMap
public inline operator fun <V> MutableMap<in String, in V>.setValue(
    thisRef: Any?, property: KProperty<*>, value: V
) { this.put(property.name, value) }

关键:属性名 = Map 的 key。所以 val name: String by map 会读 map["name"]。

典型应用场景:

// 从 JSON 反序列化的 map 创建对象
val json: Map<String, Any> = parseJson(text)
val user = User(json)                       // 属性自动从 map 取

// 从 SharedPreferences 读设置
class Settings(prefs: SharedPreferences) {
    val theme: String by prefs.all
    val fontSize: Int by prefs.all
}

风险:类型不匹配时 ClassCastException——因为 getValue 用了 unchecked cast。生产环境要配合数据校验。


# 7. 自定义属性委托

# 7.1 ReadWriteProperty接口

自定义可读写属性委托的最简写法——实现 ReadWriteProperty<Any?, T>:

class LoggedProp<T>(private var value: T) : ReadWriteProperty<Any?, T> {
    override fun getValue(thisRef: Any?, prop: KProperty<*>): T {
        println("get ${prop.name} = $value")
        return value
    }
    override fun setValue(thisRef: Any?, prop: KProperty<*>, new: T) {
        println("set ${prop.name}: $value -> $new")
        value = new
    }
}

class Config {
    var timeout by LoggedProp(30)
}

val c = Config()
c.timeout = 60      // set timeout: 30 -> 60
val t = c.timeout   // get timeout = 60

两个标准接口:

接口 用途
ReadOnlyProperty<R, T> val 属性(只需 getValue)
ReadWriteProperty<R, T> var 属性(需要 getValue + setValue)

不实现这两个接口也可以——只要有 operator fun getValue/setValue 匹配签名即可(约定优于接口)。

# 7.2 provideDelegate高级

provideDelegate 让委托的创建也可参与——比如在属性绑定时做校验:

class Preference<T>(val default: T) {
    operator fun provideDelegate(
        thisRef: Any?, prop: KProperty<*>
    ): ReadWriteProperty<Any?, T> {
        // ← 在属性初始化时调用一次(不是每次 getter)
        checkPreference(prop.name)      // 校验属性名合法性
        return PreferenceImpl(prop.name, default)
    }
}

class Settings {
    var name by Preference("default")   // provideDelegate 被调用,返回真正的委托
}

执行时机:

类初始化时
  ↓
name$delegate = Preference("default").provideDelegate(this, KProperty)
  ↓                                    (返回 PreferenceImpl)
                                        ↓
每次 name 的 getter/setter
  ↓
name$delegate.getValue(this, KProperty)

核心价值:provideDelegate 让委托知道自己在绑定给谁——可以做校验、注册、初始化等一次性工作。

典型场景:

// 数据库属性绑定
class DBColumn<T>(val type: String) {
    operator fun provideDelegate(
        thisRef: Table, prop: KProperty<*>
    ): ReadWriteProperty<Table, T> {
        // 在类初始化时注册列到 schema
        thisRef.registerColumn(prop.name, type)
        return ColumnImpl(prop.name)
    }
}

# 7.3 委托的委托

委托可以委托给另一个委托——高级组合能力:

class DebugDelegate<T>(private val inner: ReadWriteProperty<Any?, T>) : ReadWriteProperty<Any?, T> {
    override fun getValue(thisRef: Any?, prop: KProperty<*>): T {
        val v = inner.getValue(thisRef, prop)
        println("[DEBUG] read ${prop.name} = $v")
        return v
    }
    override fun setValue(thisRef: Any?, prop: KProperty<*>, new: T) {
        println("[DEBUG] write ${prop.name} = $new")
        inner.setValue(thisRef, prop, new)
    }
}

class Config {
    // 包装 observable:先 debug 再触发 observable
    var timeout: Int by DebugDelegate(
        Delegates.observable(30) { _, old, new -> updateUI(old, new) } as ReadWriteProperty<Any?, Int>
    )
}

使用场景:给现有委托加装横切关注点(日志、埋点、性能计时)——装饰器模式在委托层的应用。

# 7.4 Preferences示例

完整例子——Android SharedPreferences 的属性委托封装:

class PreferenceDelegate<T>(
    private val prefs: SharedPreferences,
    private val key: String,
    private val default: T
) : ReadWriteProperty<Any?, T> {
    
    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any?, prop: KProperty<*>): T = when (default) {
        is String -> prefs.getString(key, default) as T
        is Int -> prefs.getInt(key, default) as T
        is Long -> prefs.getLong(key, default) as T
        is Boolean -> prefs.getBoolean(key, default) as T
        is Float -> prefs.getFloat(key, default) as T
        else -> throw IllegalArgumentException("Unsupported type: ${default!!::class}")
    }
    
    override fun setValue(thisRef: Any?, prop: KProperty<*>, value: T) {
        prefs.edit().apply {
            when (value) {
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Long -> putLong(key, value)
                is Boolean -> putBoolean(key, value)
                is Float -> putFloat(key, value)
                else -> throw IllegalArgumentException("Unsupported type: ${value!!::class}")
            }
        }.apply()
    }
}

// 使用
class AppSettings(prefs: SharedPreferences) {
    var theme by PreferenceDelegate(prefs, "theme", "light")
    var fontSize by PreferenceDelegate(prefs, "font_size", 14)
    var notifEnabled by PreferenceDelegate(prefs, "notif_enabled", true)
}

// 调用方
val settings = AppSettings(context.getSharedPreferences("app", 0))
settings.theme = "dark"                     // 自动写入 SharedPreferences
val cur = settings.theme                    // 自动从 SharedPreferences 读

收益:

  • 消除了大量 getString/putString + edit/apply 样板代码
  • 类型安全(编译期确定)
  • 属性名统一为 key,避免手写字符串常量
  • 加缓存、加变化监听都很容易

这就是委托机制的实用价值——把重复的样板代码收敛到语言层。


# 8. 委托性能代价

# 8.1 一次方法转发

类委托:每次调用多一次虚方法调用——通常被 JIT 内联,几乎零开销。

实测(JMH benchmark,一次接口方法调用):

方式 耗时 (ns) 说明
直接方法调用 1.2 基准
Kotlin 类委托(未内联) 2.5 加一次虚方法调用
Kotlin 类委托(JIT 稳态后) 1.3 JIT 内联,与直接调用无异
Java Proxy 45 反射 + InvocationHandler

结论:稳态下类委托接近零开销。冷路径(JIT 未触发)时有约 1 ns 的额外开销。

# 8.2 KProperty创建

属性委托:每次 getter 都要传 KProperty 引用;KProperty 由编译器生成、静态字段缓存——一次创建,重复使用。

内存开销:

class Foo {
    val a by MyDelegate()      // 生成 KProperty(a)
    val b by MyDelegate()      // 生成 KProperty(b)
    val c by MyDelegate()      // 生成 KProperty(c)
}

静态字段 $$delegatedProperties 里存 3 个 KProperty 实例——类加载时创建一次,永远存在。

每属性开销:

  • PropertyReference1Impl 对象:约 48 字节
  • 静态数组一格:8 字节
  • 总计:每属性 ~56 字节

10000 个委托属性 = 560 KB 静态元数据——通常不是瓶颈。

# 8.3 lazy对象额外开销

by lazy { } 生成一个 Lazy<T> 实现类的实例——每个属性一个对象。

内存对比(每属性):

方案 内存开销
手写 @Volatile var _cache: T? = null + 双检 8 字节引用
by lazy { } (SYNCHRONIZED) SynchronizedLazyImpl ~40 字节 + lambda ~16 字节 = ~56 字节
by lazy(PUBLICATION) { } SafePublicationLazyImpl ~48 字节 + lambda ~16 字节
by lazy(NONE) { } UnsafeLazyImpl ~32 字节 + lambda ~16 字节

10 万个 lazy 属性 = 5 MB 额外对象——在极端场景下需要考虑手写方案,一般情况不必优化。

替代方案:热点路径可以自己实现,一般路径 by lazy 更清晰:

// 手写等价物
class Foo {
    @Volatile private var _cache: Config? = null
    val cfg: Config
        get() {
            _cache?.let { return it }
            return synchronized(this) {
                _cache ?: parseConfig().also { _cache = it }
            }
        }
}

# 8.4 JIT能否消除

结论汇总:

委托类型 JIT 能否消除 说明
类委托 by delegate ✅ 完全消除 转发桩只有 3 条字节码,稳态后与直接调用无异
by lazy 快路径 ✅ 大部分消除 Lazy.getValue 是 inline,SynchronizedLazyImpl.value 的 volatile 读不能消除但便宜
by lazy 慢路径 ❌ 无法消除 synchronized 块必然存在
by Delegates.observable ⚠️ 部分消除 getValue 简单可以内联,setValue 里的 lambda 调用可能保留
自定义 ReadWriteProperty ⚠️ 取决于实现 简单实现被内联,复杂实现保留调用

关键:kotlin.Lazy 接口的 getValue 就是 inline 扩展函数:

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

因此 cfg 的读取字节码等价于:

GETFIELD instance$delegate
INVOKEVIRTUAL Lazy.getValue()        ; ← 会被 JIT 内联到具体子类的 value getter

稳态下 lazy 委托几乎零开销——除了 volatile 读的一次内存屏障(约 1-2 ns)。


# 9. 事故案例复盘

# 9.1 lazy_NONE死锁

NONE 模式的可见性问题——即使不"死锁",也可能读到未完全构造的对象。

典型现象:字段类型是 Config(非空),运行时读到 null——因为其它线程看到了未初始化的 Config 引用。

JMM 允许的重排序:

_value = Config()       // 逻辑上等于:
                        // 1. 分配 Config 对象内存
                        // 2. 调用构造函数初始化字段
                        // 3. _value = 对象地址

// 编译器/CPU 可能重排为:
                        // 1. 分配 Config 对象内存
                        // 3. _value = 对象地址(此时字段尚未初始化)
                        // 2. 调用构造函数初始化字段

其他线程看到步骤 3 后但步骤 2 前的状态:

  • _value 已经不是 UNINITIALIZED
  • 但 _value.timeout 仍是 Int 默认值 0
  • 或 _value.name 仍是 null(引用类型默认值)

这就是**"部分构造对象"**的经典 JMM 陷阱——volatile 或 synchronized 是唯一的防护手段。

其他线程读到 null 后触发装箱转型 NPE:

val timeout: Int = config.timeout   // 如果 timeout 字段被优化为 Integer
                                    // Integer 引用为 null
                                    // 自动拆箱触发 NPE

修复方案 3 选一:

// 方案 A:改成默认的 SYNCHRONIZED
val instance: Config by lazy { parseConfig() }

// 方案 B:手写 volatile + 双检
@Volatile private var _cache: Config? = null
val instance: Config
    get() = _cache ?: synchronized(this) {
        _cache ?: parseConfig().also { _cache = it }
    }

// 方案 C:类初始化时立即赋值(放弃 lazy)
val instance: Config = parseConfig()

推荐方案 A——默认 SYNCHRONIZED 就是为并发准备的,除非有强性能需求,不要用 NONE。

# 9.2 observable泄漏Fragment

class MyFragment : Fragment() {
    var user: User by Delegates.observable(User("init")) { _, _, _ ->
        updateUI()               // 隐式引用 Fragment.this
    }
}

问题:Lambda 捕获 this,只要 user 变量还活着(或委托对象还活着),Fragment 就无法回收。

为什么容易泄漏?

反编译上面代码:

public final class MyFragment$user$1 extends Lambda implements Function3<...> {
    final MyFragment this$0;          // ← 捕获 Fragment
    
    public MyFragment$user$1(MyFragment f) { this$0 = f; }
    
    public Object invoke(...) {
        this$0.updateUI();
        return Unit.INSTANCE;
    }
}

Lambda 匿名类持有 MyFragment 引用——委托对象 → lambda → Fragment,如果委托对象被静态字段或长生命周期对象持有,Fragment 就永远回收不了。

正确姿势 3 种:

// 方案 A:用 WeakReference
class MyFragment : Fragment() {
    private val userDelegate = ObservableDelegate<User>(User("init")) { new ->
        weakThis.get()?.updateUI()
    }
    var user: User by userDelegate
    private val weakThis = WeakReference(this)
}

// 方案 B:viewLifecycleOwner 自动清理
class MyFragment : Fragment() {
    var user: User by Delegates.observable(User("init")) { _, _, _ ->
        if (view != null) updateUI()      // 防御性检查
    }
}

// 方案 C:Flow / StateFlow(推荐现代方案)
class MyFragment : Fragment() {
    private val _user = MutableStateFlow(User("init"))
    var user: User
        get() = _user.value
        set(value) { _user.value = value }
    
    override fun onViewCreated(v: View, s: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                _user.collect { updateUI(it) }
            }
        }
    }
}

推荐方案 C——用协程 + Flow 替代 observable,生命周期自动管理,无泄漏风险。

# 9.3 类委托绕过override

class Repo : IRepo {
    override fun save(): Int { println("Repo.save"); return 1 }
    fun helper(): Int = save()      // ⚠ 内部调用 save
}

class Logging(delegate: Repo) : IRepo by delegate {
    override fun save(): Int { println("Logging.save"); return 999 }
    // helper 不是 IRepo 接口方法,Logging 没有它
}

val logging = Logging(Repo())
logging.save()             // 输出 "Logging.save",返回 999 ✅
logging.helper()           // ⚠ 编译不通过(helper 不在 IRepo 接口中)

// 假设让 helper 也在 IRepo 接口中:
interface IRepo { fun save(): Int; fun helper(): Int }
// 那么:
val logging = Logging(Repo())
logging.helper()           // 输出 "Repo.save"(不是 Logging.save!)返回 1

根因图解:

logging.helper()
    ↓
$$delegate_0.helper()           ; 转发到 Repo 实例
    ↓
Repo.helper() { return save() } ; 这里的 save() 是 Repo 自己的 save
    ↓
Repo.save()                     ; 返回 1,不经过 Logging.save

核心问题:类委托只做接口方法的转发——Repo 内部方法之间的调用看不到 Logging 的存在。this 指针在 Repo 里就是 Repo,不是 Logging。

对比继承的差异:

// 如果用继承(假设 Repo 是 open class)
class Logging : Repo() {
    override fun save(): Int { return 999 }
}
val logging = Logging()
logging.helper()   // 输出 "Logging.save" 返回 999
                   // 因为 helper 里的 save() 走虚方法分派,命中 Logging.save

继承和委托的核心差异表:

特性 继承 委托
this 指针 是子类实例 委托的是"另一个对象"
内部方法调用 走虚方法分派(多态) 在委托对象内闭环
覆写生效范围 所有子类方法调用 只对外部直接调用生效
灵活性 需要 open/final 标记 无需修改被委托类

结论:委托不是继承的替代品——两者有本质差异。选择时按需权衡:

  • 需要"内部调用感知覆写" → 用继承
  • 需要"零侵入组合" → 用委托
  • 大部分场景优先考虑委托(Effective Java 第 18 条)

# 10. 综合案例串讲

# 10.1 案例真相揭晓

回到 1.1 节的 6 个疑问:

  1. by lazy 生成一个 Lazy<T> 实现字段 + KProperty 静态引用——每属性一个对象,约 56 字节。
  2. SYNCHRONIZED 用 volatile + 双重检查锁;PUBLICATION 用 CAS 允许并发但只发布一个;NONE 无同步,只能单线程访问。
  3. 属性委托 by 与类委托 by 是不同的编译期机制——只是关键字复用。前者委托 getter/setter,后者委托接口方法。
  4. Delegates.observable 用继承 ObservableProperty 的模板方法模式——Lambda 里若引用 this 就容易泄漏。
  5. KProperty 是编译器生成的属性元数据引用——每属性一个静态实例,运行时用于反射与委托传参。
  6. 类委托是编译期方法转发,Java Proxy 是运行时字节码动态生成——前者更快、更类型安全,后者更灵活。

案例根因:lazy(NONE) 无 volatile / synchronized 保证——线程 A 完成初始化后写 _value 字段,但字段写入未提供 happens-before 保证;线程 B 读到部分初始化的 Config 对象,其内部字段仍为默认值 0 / null,装箱后拆箱触发 NPE。

修复:

  1. 改成默认的 by lazy { }(SYNCHRONIZED)——最简单
  2. 或用 volatile 字段 + 手写双重检查——性能有需求时
  3. 绝不用 NONE 除非能证明单线程访问——如 View 绑定、Activity 生命周期内的 lazy

# 10.2 一个lazy委托一生

写下 val cfg: Config by lazy { parseConfig() }
    ↓
kotlinc 编译期:
  ├─ 生成 cfg$delegate: Lazy<Config> 字段
  ├─ 生成 $$delegatedProperties[N] 静态 KProperty 数组
  ├─ 类初始化:cfg$delegate = LazyKt.lazy { parseConfig() }
  │  └─ 默认调用 kotlin.LazyKt__LazyJVMKt.lazy(initializer)
  │      └─ 返回 SynchronizedLazyImpl(initializer, null)
  └─ 生成 getCfg() { return cfg$delegate.getValue(this, prop) }
      └─ Lazy.getValue 是 inline,实际调用 SynchronizedLazyImpl.value getter
    ↓
运行期首次调用 getCfg():
  ├─ SynchronizedLazyImpl.value getter
  ├─ 快路径检查:_value volatile 读 == UNINITIALIZED?是
  ├─ synchronized(lock) 加锁
  ├─ 慢路径检查:_value == UNINITIALIZED?是
  ├─ 调用 initializer()(用户 lambda)
  ├─ 存入 _value(volatile 写,触发 happens-before)
  ├─ initializer = null(释放 lambda 引用)
  └─ 释放锁,返回结果
    ↓
后续调用:
  ├─ 快路径检查:_value != UNINITIALIZED(99.99% 命中)
  └─ 直接返回 _value(一次 volatile 读,约 1-2 ns)
    ↓
JIT 稳态后:
  ├─ Lazy.getValue 内联
  ├─ SynchronizedLazyImpl.value getter 内联
  └─ 最终字节码几乎等价于 return this.cfg$delegate._value(带 volatile 语义)

一次编译,三个阶段消费——编译期生成、类加载期实例化、运行期快慢双路径。

# 10.3 设计哲学回扣

  • 组合优于继承(Effective Java Item 18):by 关键字让"has-a"变成一等公民——一个关键字消除大量样板代码
  • 约定优于配置:getValue/setValue 是约定不是接口——灵活性与类型安全并存;任何对象加上扩展函数就能变成委托
  • 编译期完成,零反射:Kotlin 委托机制的运行时开销极小——不像 Java Proxy 那样牺牲性能换灵活
  • 让危险模式显式化:LazyThreadSafetyMode.NONE 强迫开发者显式选择——不做默默假设,避免误用
  • 标准库示范:ObservableProperty 用模板方法模式,Delegates.observable/vetoable 用继承衍生——是 GoF 设计模式的教科书应用

# 10.4 委托速查表

场景 推荐
单线程延迟初始化 by lazy(NONE)
多线程延迟初始化 by lazy { }
无副作用可并发初始化 by lazy(PUBLICATION)
属性变化监听 Delegates.observable 或 StateFlow
属性变化否决 Delegates.vetoable
无初始值非空 var(引用类型) lateinit var
无初始值非空 var(基本类型) Delegates.notNull()
从 Map 取值 by map
从 SharedPreferences 取值 自定义 PreferenceDelegate
组合多接口 类委托 by delegateN
需要拦截创建 provideDelegate
装饰现有委托 委托的委托(装饰器模式)

lazy 三模式选择速查:

场景 模式
确定单线程访问(如 View 绑定) NONE
多线程访问 + 有副作用 SYNCHRONIZED(默认)
多线程访问 + 纯函数 initializer PUBLICATION
不确定 SYNCHRONIZED(安全默认)

委托机制口诀:

  1. by 有两义:类委托委托方法,属性委托委托 get/set
  2. getValue/setValue 是约定:不是接口,operator 修饰即可
  3. lazy 默认安全:SYNCHRONIZED 是防御性设计
  4. 委托不是继承:内部方法调用不走委托覆写
  5. 零反射不代表零开销:委托对象、KProperty 都要内存

下一步:至此 Kotlin 精通模块的 5 篇核心原理专栏全部完成。回到 模块总览 温习学习路线,或者拿一个真实 Android 项目实践你新学到的一切——每次看到 ?. by lazy data class launch inline,你应该都能"看见"它们编译后的样子。

上次更新: 2026/07/05, 15:10:54
inline与reified原理
README

← inline与reified原理 README→

最近更新
01
11.数据库的原理总结
07-12
02
11.操作系统原理总结
07-10
03
资深程序员软能力
07-10
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式