委托(Delegation) 是一种 设计模式,用于让一个对象将其某些功能委托给另一个对象,无需继承即可复用已有功能。在 Java 中,委托通常是手动实现的。例如,我们想让 MyHashSet 复用 HashSet 的功能,但又希望扩展一些新功能,比如计数 add() 方法被调用的次数,一般我们会这样实现:

import java.util.HashSet;
 
class MyHashSet<E> {
 
    private final HashSet<E> delegate = new HashSet<>();
    private int addCount = 0;
 
    public boolean add(E element) {
        addCount++;
        return delegate.add(element);
    }
 
    public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return delegate.addAll(c);
    }
 
    // 新增添加 add() 计数方法
    public int getAddCount() {
        return addCount;
    }
        ...
    // 所有的基础集合操作都需要重写,因为不重写没法用
}

这里我们新增了一个add()计数方法,可以看到其他方法都是调用 delegate 来实现的。但是我们为了 MyHashSet 可用,我们会重写非常多的基础方法。这就导致了两个缺点:

  • 样板代码多:我们需要手动 编写所有 HashSet 的方法add()addAll() 等),即使只想修改部分功能。
  • 不够灵活:如果 HashSet 方法新增了,我们必须手动更新 MyHashSet

Kotlin 委托

Kotlin 通过 by 关键字类委托 变得更加简洁。同时,Kotlin 提供了 类委托(Class Delegation)属性委托(Property Delegation) 两种方式来简化代码。

类委托(Class Delegation)

类委托 允许一个类将 接口的实现 委托给另一个对象,而不是自己去实现所有的方法。Kotlin 通过 by** 关键字** 实现这一功能。比如,可以直接 HashSet 的实现委托 给另一个 HashSet 实例:

// 将MutableSet接口委托给delegate
class CountingHashSet<E>(private val delegate: HashSet<E> = HashSet()) : MutableSet<E> by delegate {
    // 自定义计数器
    var addCount = 0
    // 私有 setter 方法
    private set
 
    // 重写add方法
    override fun add(element: E): Boolean {
        addCount++ // 每次添加元素时计数器+1
        return delegate.add(element) // 委托给HashSet实际执行添加
    }
 
    override fun addAll(elements: Collection<E>): Boolean {
        addCount += elements.size // 按添加元素数量增加计数器
        return delegate.addAll(elements) // 委托给HashSet实际执行批量添加
    }
}
 
fun main() {
    val set = CountingHashSet<Int>()
    set.addAll(listOf(1, 2, 3, 4))
 
    println(set.addCount) // 输出: 4
}

by delegate表示将Set接口的实现委托给delegate(即HashSet),而大部分基础集合操作(如remove,contains等)通过by关键字自动代理,直接由HashSet处理,无需重复实现。我们只需要关注我们需要重写的方法即可。

Kotlin 属性委托(Property Delegation)

在 Java 中,我们如果想要延迟初始化一个变量,主要突出一个**“按需加载”**,通常使用 null 检查:

class LazyExample {
    private String name;
 
    public String getName() {
        if (name == null) {
            name = "Kotlin";
        }
        return name;
    }
}

by lazy

在 Kotlin 中,我们可以使用 **属性委托 **by lazy,让变量在第一次访问时 自动初始化

class LazyExample {
 
    // Kotlin 的 lazy 委托允许你在属性中直接编写初始化逻辑
    val name: String by lazy {
        println("初始化 name")
        "Kotlin"
    }
}
 
fun main() {
    val example = LazyExample()
    println("访问 name 之前")
    println(example.name) // 触发 lazy 初始化
    println(example.name) // 第二次访问,不会重新初始化
}

运行结果

访问 name 之前
初始化 name
Kotlin
Kotlin

这里 by lazy 并不是一个连在一起的关键字,只有by是关键字。

by lazy 工作原理解密

Kotlin 的 by lazy 本质上是 属性委托(Property Delegation),它的作用是 第一次访问属性时计算值并缓存,后续访问直接返回缓存值

我们自己实现一个 myLazy,核心逻辑就是 延迟初始化 + 缓存值

a. 非线程安全版 later

package 委托
 
import kotlin.reflect.KProperty
 
/**
 * @author zhangzhen40
 * @version
 * @since 2025/03/17
 */
class Later<T>(private val block: () -> T) {
 
    private var cachedValue: Any? = null
 
    /**
     * 当访问委托属性时,Kotlin 会自动调用 getValue 方法来获取属性的值
     * 固定写法,Kotlin 委托属性的标准约定
     *
     * @param thisRed Any? 委托属性的接收者(即属性所属的对象)
     * @param property KProperty<*> 委托属性的元信息(如属性名称、类型等)
     * @return T
     */
    operator fun getValue(thisRed: Any?, property: KProperty<*>): T {
        if (cachedValue == null) {
            println("\uD83D\uDD04 计算属性值: ${property.name}")
            cachedValue = block()
        }
        return cachedValue as T
    }
}
 
fun <T> later(block: () -> T) = Later(block)
 
// 使用自定义的 by lazy
class Example() {
    val value: String by later {
        println("🛠️ 正在初始化 value")
        "Kotlin is awesome!"
    }
}
 
fun main() {
    val example = Example()
    println("访问 value 之前")
    println(example.value) // 第一次访问,计算并缓存
    println(example.value) // 第二次访问,直接返回缓存值
}

代码执行过程中发生了什么?

当创建 Example 的实例时,由于 value 是一个委托属性(使用了 by later),它的初始化被委托给了 Later 类的实例。Later 的构造函数接收一个 block() 参数,但这个 block 并不会立即执行。LatergetValue 方法中,只有在第一次访问 value 时,才会执行 block 并缓存结果。因此,value 的初始化被推迟到了第一次访问它的时候。

b. 线程安全版 later

class Later<T>(private val block: () -> T, private val strategy: LaterStrategy<T>) {
    private var cachedValue: Any? = null
 
    // 双重检验锁
    operator fun getValue(thisRed: Any?, property: KProperty<*>): T {
        return cachedValue as T ?: synchronized(this) {
            cachedValue as T ?: block().also { cachedValue = it }
        }
    }
}

c. 闲的没事干版 later

package 委托
 
import kotlin.reflect.KProperty
 
/**
 * 策略+工厂实现一个根据健壮性的 Later
 *
 * @author zhangzhen40
 * @version
 * @since 2025/03/17
 */
 
/**
 * 策略接口
 *
 * @param T
 */
interface LaterStrategy<T> {
    operator fun getValue(thisRed: Any?, property: KProperty<*>): T
}
 
/**
 * 非线程安全的 Later
 *
 * @param T
 * @property block Function0<T>
 * @property cachedValue Any?
 * @constructor
 */
class Later<T>(private val block: () -> T) : LaterStrategy<T> {
 
    private var cachedValue: Any? = null
 
    /**
     * 当访问委托属性时,Kotlin 会自动调用 getValue 方法来获取属性的值
     * 固定写法,Kotlin 委托属性的标准约定
     *
     * @param thisRed Any? 委托属性的接收者(即属性所属的对象)
     * @param property KProperty<*> 委托属性的元信息(如属性名称、类型等)
     * @return T
     */
    override operator fun getValue(thisRed: Any?, property: KProperty<*>): T {
        if (cachedValue == null) {
            println("\uD83D\uDD04 计算属性值: ${property.name}")
            cachedValue = block()
        }
        return cachedValue as T
    }
}
 
/**
 * 线程安全的 Later
 *
 * @param T
 * @property block Function0<T>
 * @property cachedValue Any?
 * @constructor
 */
class SafeLater<T>(private val block: () -> T) : LaterStrategy<T> {
 
    @Volatile
    private var cachedValue: Any? = null
 
    override operator fun getValue(thisRed: Any?, property: KProperty<*>): T {
        return cachedValue as T ?: synchronized(this) {
            cachedValue as T ?: block().also { cachedValue = it }
        }
    }
}
 
/**
 * 新学的技巧,使用 sealed class 代替 enum
 */
sealed class LaterMode {
    object Normal : LaterMode()                                   // 非线程安全
    object ThreadSafe : LaterMode()                               // 线程安全
    class Custom<T>(val strategy: LaterStrategy<T>) : LaterMode() // 允许用户自定义策略
}
 
/**
 * 懒加载工厂
 */
object LaterFactory {
    fun <T> create(mode: LaterMode, block: () -> T): LaterStrategy<T> {
        return when (mode) {
            is LaterMode.Custom<*> -> mode.strategy as LaterStrategy<T> // 处理 Custom 模式
            is LaterMode.Normal -> Later(block)
            is LaterMode.ThreadSafe -> SafeLater(block)
        }
    }
}
 
/**
 * 懒加载顶层函数
 *
 * @param block Function0<T>
 * @param mode LaterMode
 * @return Later<T>
 */
fun <T> later(mode: LaterMode = LaterMode.Normal, block: () -> T) = LaterFactory.create(mode, block)
 
// 使用自定义的 by lazy
class Example {
    val value: String by later {
        println("🛠️ 正在初始化 value")
        "Kotlin is awesome!"
    }
}
 
fun main() {
    val example = Example()
    println("访问 value 之前")
    println(example.value) // 第一次访问,计算并缓存
    println(example.value) // 第二次访问,直接返回缓存值
}

Delegates.observable

在 Kotlin 中,Delegates.observable属性委托(Property Delegation) 的一种,属于 kotlin.properties.Delegates,用于 监听属性变化

当属性值发生变化时,它会触发回调 beforeChange -> afterChange,你可以在回调中执行 日志、UI 刷新、触发事件 等操作。

a. Delegates.observable:监听属性变化

import kotlin.properties.Delegates
 
class User {
    // 使用 observable 委托
    var name: String by Delegates.observable("") { prop, old, new ->
        println("${prop.name} 从 $old 改为 $new") // 属性变化时的回调
    }
}
 
fun main() {
    val user = User()
    user.name = "Alice"   // 输出: name 从  改为 Alice
    user.name = "Bob"     // 输出: name 从 Alice 改为 Bob
}

name 的初始值是空字符串 "",当 name 值发生变化时,会触发 observable 的属性回调。

运行结果

name 从  改为 Alice
name 从 Alice 改为 Bob

b. Delegates.vetoable:允许拒绝赋值

observable 只是 监听变化,如果你想在 值修改前拦截它(比如不能为空),可以使用 Delegates.vetoable。假设你有一个 SafeStorage 类,希望确保 value 属性不为空。

import kotlin.properties.Delegates
 
class SafeStorage {
    var value: String by Delegates.vetoable("") { _, old, new ->
        if (new.isEmpty()) false else true  // 拒绝空值
    }
}
 
fun main() {
    val storage = SafeStorage()
    storage.value = "Hello"   // 成功赋值
    storage.value = ""        // 被拒绝,保持原值"Hello"
}

更新: 2025-08-07 09:51:22
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/etdr7mnd5dghhvsc


相关笔记