Kotlin 按关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的委托模式使用 by
关键字实现。它允许组合而非继承和属性委托。本教程将通过实际例子深入探讨 by
关键字。
基本定义
Kotlin 中的 by
关键字支持委托。它有两个主要用途:类委托和属性委托。类委托允许您通过委托给另一个对象来实现接口。属性委托将属性访问委托给另一个对象。
类委托
类委托允许通过委托给另一个对象来实现接口。这对于装饰器模式非常有用,无需样板代码。
package com.zetcode interface SoundMaker { fun makeSound() } class Dog : SoundMaker { override fun makeSound() = println("Woof!") } class LoudAnimal(private val animal: SoundMaker) : SoundMaker by animal { fun makeLoudSound() { println("!!!") makeSound() println("!!!") } } fun main() { val loudDog = LoudAnimal(Dog()) loudDog.makeLoudSound() }
这里,LoudAnimal
通过委托给提供的 animal 来实现 SoundMaker
接口。 by
关键字处理所有接口方法调用。我们添加新功能,而无需修改原始类。
属性委托基础
属性委托将 getter/setter 调用委托给另一个对象。委托必须实现 getValue
,并可选地实现 setValue
。
package com.zetcode import kotlin.reflect.KProperty class SimpleDelegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "Delegate for '${property.name}'" } } class Example { val message by SimpleDelegate() } fun main() { val example = Example() println(example.message) // Output: Delegate for 'message' }
SimpleDelegate
为 message
提供值。 by
关键字将属性连接到其委托。访问属性时,会调用委托的 getValue
。
延迟初始化
lazy
委托仅在首次访问时初始化属性。这对于可能不需要的昂贵的初始化很有用。
package com.zetcode class HeavyObject { init { println("Heavy object created") } } class Container { val heavy by lazy { HeavyObject() } } fun main() { val container = Container() println("Container created") container.heavy // Initialization happens here }
heavy
属性仅在首次访问时初始化。输出显示“Container created”(容器已创建)在“Heavy object created”(重对象已创建)之前。这演示了延迟初始化。
可观察属性
Delegates.observable
委托允许观察属性更改。每当属性值更改时,它都会调用一个处理程序。
package com.zetcode import kotlin.properties.Delegates class User { var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main() { val user = User() user.name = "John" // Output: <no name> -> John user.name = "Jane" // Output: John -> Jane }
每次 name
更改时,处理程序都会打印旧值和新值。 by
关键字将属性连接到可观察委托。这对于更改跟踪很有用。
可否决属性
Delegates.vetoable
委托允许拒绝属性更改。处理程序可以返回 false 以否决更改。
package com.zetcode import kotlin.properties.Delegates class PositiveNumber { var value: Int by Delegates.vetoable(0) { _, old, new -> new >= 0 } } fun main() { val num = PositiveNumber() num.value = 42 println(num.value) // Output: 42 num.value = -1 println(num.value) // Output: 42 (change rejected) }
委托仅接受非负数。尝试设置 -1 会被拒绝,保留先前的值。 by
关键字启用了这种验证行为。
自定义属性委托
您可以通过实现属性运算符函数来创建自定义委托。这允许完全控制属性访问。
package com.zetcode import kotlin.reflect.KProperty class RangeDelegate( private val min: Int, private val max: Int, private var value: Int ) { operator fun getValue(thisRef: Any?, property: KProperty<*>): Int { return value } operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) { if (newValue in min..max) { value = newValue } } } class Config { var setting by RangeDelegate(0, 100, 50) } fun main() { val config = Config() println(config.setting) // Output: 50 config.setting = 75 println(config.setting) // Output: 75 config.setting = 150 println(config.setting) // Output: 75 (unchanged) }
RangeDelegate
将值限制为指定的范围。 by
关键字将属性连接到我们的自定义委托。无效值将被静默忽略,保留先前有效值。
Map 委托
属性可以委托给一个 map,属性名称作为键。这对于动态属性访问和类似 JSON 的结构很有用。
package com.zetcode class User(val map: Map<String, Any?>) { val name: String by map val age: Int by map } fun main() { val user = User(mapOf( "name" to "John Doe", "age" to 30 )) println("${user.name}, ${user.age}") // Output: John Doe, 30 }
User
类将属性委托给提供的 map 中的值。 by
关键字将每个属性连接到其对应的 map 条目。此模式经常与 JSON 反序列化一起使用。
委托的最佳实践
- 首选委托而非继承: 使用类委托来避免脆弱的基类问题。
- 使用标准委托: 利用
lazy
、observable
和vetoable
来实现常见模式。 - 创建自定义委托: 为特定于领域的行为实现您自己的委托。
- 考虑性能: 委托增加了间接性;确保它不会影响关键路径。
- 文档行为: 清楚地记录自定义委托的行为,因为它不可立即看到。
来源
本教程深入介绍了 Kotlin 的 by
关键字,展示了类委托和属性委托模式。我们探索了标准委托并创建了自定义委托。委托是 Kotlin 中代码重用和关注点分离的强大工具。
作者
列出 所有 Kotlin 教程。