在 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 监听变化并启动协程处理 |
通过委托模式,可在协程中更优雅地实现代码复用和逻辑解耦,尤其适合处理异步场景下的属性管理和接口实现。