Kotlin 委托:by 关键字与委托属性
在 Kotlin 中,委托是一种设计模式,允许一个对象(委托者)将某些操作“转交”给另一个对象(被委托者)处理。这种机制通过 by 关键字实现,能显著减少样板代码,提升代码复用性和可维护性。委托分为两类:类委托(Class Delegation)和委托属性(Delegated Properties)。本文将手把手教你如何使用它们。
使用类委托简化接口实现
当你需要实现一个接口,但大部分逻辑与其他已有类相同,可以用类委托避免重复编写代码。
-
定义一个接口,例如
Printer:interface Printer { fun print(message: String) } -
创建一个已实现该接口的类,比如
ConsolePrinter:class ConsolePrinter : Printer { override fun print(message: String) { println("Console: $message") } } ``` 3. **使用 `by` 关键字委托实现**。新建一个类 `LoggedPrinter`,它不自己实现 `print` 方法,而是把调用交给内部的 `ConsolePrinter` 实例: ```kotlin class LoggedPrinter(printer: Printer) : Printer by printer ``` 4. **调用时自动转发**。当你调用 `LoggedPrinter` 的 `print` 方法,实际执行的是 `ConsolePrinter` 的实现: ```kotlin val printer = LoggedPrinter(ConsolePrinter()) printer.print("Hello") // 输出:Console: Hello ``` **关键点**:`by` 后面跟的是一个**已存在的对象实例**,Kotlin 编译器会自动生成接口所有方法的转发代码,你无需手动重写。 --- ### 使用委托属性管理变量行为 委托属性用于将属性的 getter/setter 逻辑抽离到独立的类中。常见场景包括延迟初始化、监听属性变化、映射到 Map 等。 #### 场景一:延迟初始化(lazy) 当你有一个耗资源的对象,希望首次访问时才创建: 1. **使用 `by lazy` 声明属性**: ```kotlin val expensiveObject: String by lazy { println("Initializing...") "Computed Value" } ``` 2. **首次读取时触发初始化**: ```kotlin println(expensiveObject) // 输出:Initializing... 和 Computed Value println(expensiveObject) // 仅输出:Computed Value(后续不再初始化) ``` **注意**:`lazy` 是线程安全的默认实现,适用于单例或只读场景。 #### 场景二:监听属性变化(observable) 当你需要在属性值改变时执行回调: 1. **用 `Delegates.observable` 声明属性**,并提供初始值和监听函数: ```kotlin import kotlin.properties.Delegates var name: String by Delegates.observable("Alice") { prop, old, new -> println("$prop changed from $old to $new") } -
赋值时自动触发回调:
name = "Bob" // 输出:property name changed from Alice to Bob
回调参数说明:
prop:被观察的属性引用(类型为KProperty<*>)old:旧值new:新值
场景三:将属性存储到 Map(map-based delegation)
当你有一组动态属性,想用 Map 统一管理(如解析 JSON 或配置):
-
准备一个 MutableMap 容器:
val userConfig = mutableMapOf<String, Any?>( "username" to "john_doe", "age" to 30 ) -
声明委托属性,绑定到 Map 的 key:
var username: String by userConfig var age: Int by userConfig -
读写属性即操作 Map:
println(username) // 输出:john_doe age = 31 println(userConfig["age"]) // 输出:31
要求:Map 的 key 必须与属性名一致,且泛型兼容。
自定义委托属性
Kotlin 允许你编写自己的委托逻辑。只需实现 getValue 和 setValue(针对 var)函数。
-
创建一个委托类,例如实现一个带范围检查的整数属性:
class RangedInt(private val min: Int, private val max: Int) { private var value = min operator fun getValue(thisRef: Any?, property: KProperty<*>): Int { return value } operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) { if (newValue !in min..max) { throw IllegalArgumentException("Value out of range [$min, $max]") } value = newValue } } -
在类中使用该委托:
class Thermostat { var temperature: Int by RangedInt(15, 30) } -
测试边界行为:
val thermo = Thermostat() thermo.temperature = 25 // 正常 thermo.temperature = 40 // 抛出异常:Value out of range [15, 30]
规则:
- 对于
val属性,只需提供getValue - 对于
var属性,必须同时提供getValue和setValue - 函数必须标记为
operator
常见内置委托工具汇总
Kotlin 标准库提供了多个实用委托,无需额外依赖:
| 委托函数 | 用途 | 适用属性类型 |
|---|---|---|
lazy() |
延迟初始化,线程安全 | val |
Delegates.observable() |
监听值变化 | var |
Delegates.vetoable() |
可阻止赋值的监听器 | var |
Delegates.notNull() |
非空延迟初始化(需手动赋值) | var |
| Map 实例 | 将属性映射到 Map 的 key | var / val |
使用 Delegates.notNull() 示例:
class Person {
var name: String by Delegates.notNull()
}
// 必须在使用前赋值,否则读取时报错
注意事项与最佳实践
- 避免过度使用:委托虽简洁,但会增加间接层。仅在逻辑复用明显时使用。
- 委托对象生命周期:确保被委托的对象在属性访问期间有效,避免内存泄漏。
- 线程安全:
lazy默认是线程安全的(LazyThreadSafetyMode.SYNCHRONIZED),若在单线程环境可改用LazyThreadSafetyMode.NONE提升性能:val cache by lazy(LazyThreadSafetyMode.NONE) { createCache() } - 委托属性不能是抽象的:你不能在接口中声明委托属性,因为委托逻辑依赖具体实现。
通过合理使用 by 关键字和委托属性,你可以写出更简洁、更易维护的 Kotlin 代码,将通用逻辑集中管理,让业务代码聚焦核心功能。

暂无评论,快来抢沙发吧!