在 Kotlin 中,委托(Delegation) 是一种设计模式,核心思想是 "将一个类 / 属性的部分职责委托给另一个对象",通过 by 关键字实现。协程(Coroutines)本身与委托没有直接关联,但委托模式可以与协程结合使用(例如在委托逻辑中调用 suspend 函数)。下面详细讲解 Kotlin 中的类委托 和属性委托,以及它们与协程的结合场景。
一、类委托(Class Delegation)
类委托通过 by 关键字将接口的实现委托给另一个对象,避免手动实现接口的所有方法,减少代码冗余。
基本语法
kotlin
interface Base {
    fun doSomething()
    fun doAnotherThing()
}
class BaseImpl : Base {
    override fun doSomething() {
        println("BaseImpl doSomething")
    }
    override fun doAnotherThing() {
        println("BaseImpl doAnotherThing")
    }
}
// 类 Derived 将 Base 接口的实现委托给 baseImpl 对象
class Derived(base: Base) : Base by base {
    // 可以重写委托对象的方法(可选)
    override fun doSomething() {
        println("Derived doSomething (before delegation)")
        // 调用委托对象的原方法(可选)
        (this as Base).doSomething() // 或直接使用 base.doSomething()
    }
}
fun main() {
    val base = BaseImpl()
    val derived = Derived(base)
    derived.doSomething() // 输出:Derived doSomething (before delegation) → BaseImpl doSomething
    derived.doAnotherThing() // 输出:BaseImpl doAnotherThing(直接使用委托实现)
}与协程结合
如果接口包含 suspend 函数(需在协程中调用),类委托同样适用:
kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
interface CoroutineTask {
    suspend fun execute()
}
class TaskImpl : CoroutineTask {
    override suspend fun execute() {
        delay(1000) // 模拟耗时操作(协程暂停)
        println("TaskImpl executed")
    }
}
class TaskDelegator(task: CoroutineTask) : CoroutineTask by task {
    // 重写 suspend 函数
    override suspend fun execute() {
        println("TaskDelegator before execution")
        super.execute() // 调用委托对象的 suspend 函数
        println("TaskDelegator after execution")
    }
}
fun main() = runBlocking {
    val task = TaskImpl()
    val delegator = TaskDelegator(task)
    delegator.execute() 
    // 输出:
    // TaskDelegator before execution
    // (等待 1 秒)
    // TaskImpl executed
    // TaskDelegator after execution
}二、属性委托(Property Delegation)
属性委托通过 by 关键字将属性的访问逻辑(getter/setter) 委托给另一个对象,常用场景包括延迟初始化、属性变化监听、存储管理等。
委托类需实现以下接口(根据属性是否可变):
- 对于只读属性(val) :委托类需实现 ReadOnlyProperty<thisRef, T>接口,提供getValue(thisRef, property)方法。
- 对于可变属性(var) :委托类需实现 ReadWriteProperty<thisRef, T>接口,提供getValue(...)和setValue(...)方法。
1. 延迟初始化(lazy 委托)
lazy 是 Kotlin 标准库提供的属性委托,用于延迟初始化属性(首次访问时才初始化,且仅初始化一次),适合初始化成本高的对象(如网络连接、数据库实例)。
基本用法
kotlin
val expensiveResource: String by lazy {
    println("Initializing...") // 首次访问时执行
    "Resource data" // 初始化结果
}
fun main() {
    println("Before access")
    println(expensiveResource) // 首次访问:触发初始化
    println(expensiveResource) // 第二次访问:直接使用缓存值
    // 输出:
    // Before access
    // Initializing...
    // Resource data
    // Resource data
}与协程结合
lazy 默认是线程安全的 (多线程下仅初始化一次),但在协程中需注意:lazy 的初始化逻辑默认运行在触发访问的线程 / 协程中,若初始化逻辑包含 suspend 函数,需结合 runBlocking 或自定义协程安全的延迟初始化(lazy 本身不支持 suspend 初始化逻辑)。
kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
// 错误示例:lazy 初始化块中不能直接调用 suspend 函数
// val coroutineResource: String by lazy {
//     delay(1000) // 编译错误:suspend 函数不能在非 suspend 环境调用
//     "Data"
// }
// 正确示例:用 runBlocking 包装(适合简单场景,会阻塞当前线程)
val coroutineResource: String by lazy {
    runBlocking {
        delay(1000) // 协程暂停(但 runBlocking 会阻塞当前线程)
        "Coroutine resource"
    }
}
fun main() = runBlocking {
    println("Before access")
    println(coroutineResource) // 首次访问:阻塞 1 秒后输出
    println(coroutineResource) // 直接使用缓存值
}注意:
runBlocking会阻塞线程,若需非阻塞的协程延迟初始化,可自定义委托(见下文 "自定义属性委托")。
2. 监听属性变化(observable 委托)
observable 是标准库提供的委托(位于 kotlin.properties),用于监听属性值的变化(初始化和修改时触发回调)。
基本用法
kotlin
import kotlin.properties.Delegates
class User {
    // 监听 name 属性的变化
    var name: String by Delegates.observable("<initial>") { property, oldValue, newValue ->
        println("${property.name} changed: $oldValue → $newValue")
    }
}
fun main() {
    val user = User()
    user.name = "Alice" // 输出:name changed: <initial> → Alice
    user.name = "Bob"   // 输出:name changed: Alice → Bob
}与协程结合
在监听回调中可以调用协程相关逻辑(如通过 CoroutineScope 启动协程处理变化):
kotlin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.properties.Delegates
class DataHolder(scope: CoroutineScope) {
    // 监听 data 属性变化,并在 IO 线程处理
    var data: String by Delegates.observable("") { _, old, new ->
        scope.launch(Dispatchers.IO) {
            println("Processing change on ${Thread.currentThread().name}: $old → $new")
            delay(500) // 模拟异步处理(非阻塞)
            println("Processed new value: $new")
        }
    }
}
fun main() = runBlocking {
    val dataHolder = DataHolder(this)
    dataHolder.data = "First update"
    dataHolder.data = "Second update"
    delay(1000) // 等待协程处理完成
    // 输出(线程名可能不同):
    // Processing change on DefaultDispatcher-worker-1:  → First update
    // Processing change on DefaultDispatcher-worker-2: First update → Second update
    // Processed new value: First update
    // Processed new value: Second update
}3. 自定义属性委托
除了标准库提供的委托,还可以自定义委托类,实现更灵活的逻辑(如协程安全的延迟初始化、数据库存储委托等)。
示例:协程安全的延迟初始化委托
实现一个支持 suspend 初始化逻辑的延迟委托(非阻塞):
kotlin
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.reflect.KProperty
// 自定义只读属性委托:支持 suspend 初始化
class CoroutineLazy<T>(
    private val initializer: suspend () -> T
) {
    private var value: T? = null
    private val mutex = Mutex() // 协程安全锁(避免并发初始化)
    suspend operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (value == null) {
            mutex.withLock { // 确保初始化逻辑仅执行一次
                if (value == null) {
                    value = initializer()
                }
            }
        }
        return value!!
    }
}
// 便捷函数:创建 CoroutineLazy 实例
fun <T> coroutineLazy(initializer: suspend () -> T) = CoroutineLazy(initializer)
// 使用示例
class Repository {
    // 用自定义委托延迟初始化(支持 suspend 逻辑)
    val remoteData: String by coroutineLazy {
        println("Fetching data from network...")
        delay(1000) // 模拟网络请求(非阻塞)
        "Remote data"
    }
}
fun main() = runBlocking {
    val repo = Repository()
    println("Before fetch")
    println(repo.remoteData) // 首次访问:触发 suspend 初始化
    println(repo.remoteData) // 直接使用缓存值
    // 输出:
    // Before fetch
    // Fetching data from network...
    // (等待 1 秒)
    // Remote data
    // Remote data
}三、委托与协程的结合场景总结
- 类委托:将接口中包含 suspend 函数的实现委托给其他类,简化代码复用(如代理网络请求、异步任务)。
- lazy委托:延迟初始化协程中需要的资源(注意线程安全,复杂场景需自定义协程安全委托)。
- observable委托 :监听属性变化时,通过- CoroutineScope启动协程处理异步逻辑(如网络请求、数据库更新)。
- 自定义委托:实现协程特有的逻辑(如非阻塞延迟初始化、协程安全的属性访问控制)。
四、核心区别
| 委托类型 | 核心作用 | 与协程结合点 | 
|---|---|---|
| 类委托 | 接口实现的复用 | 委托包含 suspend 函数的接口 | 
| 属性委托 | 简化属性的 getter/setter 逻辑 | lazy延迟初始化资源、observable监听变化并启动协程处理 | 
通过委托模式,可在协程中更优雅地实现代码复用和逻辑解耦,尤其适合处理异步场景下的属性管理和接口实现。