Kotlin 委托关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的委托模式允许对象组合来实现代码复用。 by 关键字无需样板代码即可实现委托。本教程通过实际示例探讨属性委托和类委托。
基本定义
Kotlin 中的 by 关键字实现了委托模式。 它有两种形式:类委托和属性委托。 委托有助于在类之间共享行为时避免继承。
类委托
类委托允许通过委托给另一个对象来实现接口。 当您想要扩展功能而无需继承时,这非常有用。
package com.zetcode
interface SoundMaker {
fun makeSound()
}
class Dog : SoundMaker {
override fun makeSound() = println("Woof!")
}
class Robot(private val soundMaker: SoundMaker) : SoundMaker by soundMaker {
fun move() = println("Moving...")
}
fun main() {
val dog = Dog()
val robot = Robot(dog)
robot.makeSound() // Output: Woof!
robot.move() // Output: Moving...
}
这里,Robot 通过委托给 Dog 实例来实现 SoundMaker。 Robot 类可以专注于自己的行为,同时重用 Dog 的声音实现。
懒属性委托
lazy 委托仅在首次访问时初始化属性。 这对于可能并非总是需要的高成本操作很有用。
package com.zetcode
val heavyConfiguration: String by lazy {
println("Computing heavy configuration")
"Config loaded"
}
fun main() {
println("Before access")
println(heavyConfiguration) // Output: Computing heavy configuration
println(heavyConfiguration) // Output: Config loaded (cached)
}
heavyConfiguration 仅在首次访问时计算。 后续访问将返回缓存的值。 默认情况下,初始化是线程安全的。
可观察属性委托
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 = "Alice" // Output: John → Alice
}
每次 name 属性更改时,处理程序都会打印更改。 这对于实现更改监听器而无需样板代码非常有用。
可否决属性委托
Delegates.vetoable 委托允许拒绝属性更改。 处理程序返回 true 以接受更改,或者返回 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 // Rejected
println(num.value) // Output: 42
}
vetoable 委托确保该值保持非负数。 对 -1 的更改被拒绝,保留先前的值 42。
自定义属性委托
您可以通过实现 getValue 和 setValue 运算符函数来创建自定义委托。 这允许完全控制属性访问。
package com.zetcode
import kotlin.reflect.KProperty
class TrimDelegate {
private var trimmedValue: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return trimmedValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
trimmedValue = value.trim()
}
}
class Form {
var input: String by TrimDelegate()
}
fun main() {
val form = Form()
form.input = " Hello Kotlin "
println("'${form.input}'") // Output: 'Hello Kotlin'
}
TrimDelegate 自动从字符串值中去除空格。 自定义委托处理获取和设置属性值。
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) // Output: John Doe
println(user.age) // Output: 30
}
User 类将其属性委托给一个 map。 属性名称必须与 map 键匹配。 此模式通常与 JSON 反序列化一起使用。
多接口委托
一个类可以将多个接口委托给不同的对象。 这允许组合来自多个来源的行为。
package com.zetcode
interface Flyer {
fun fly() = println("Flying")
}
interface Swimmer {
fun swim() = println("Swimming")
}
class Bird : Flyer
class Fish : Swimmer
class Duck(private val flyer: Flyer, private val swimmer: Swimmer) :
Flyer by flyer, Swimmer by swimmer {
fun quack() = println("Quack!")
}
fun main() {
val duck = Duck(Bird(), Fish())
duck.fly() // Output: Flying
duck.swim() // Output: Swimming
duck.quack() // Output: Quack!
}
Duck 通过委托给 Bird 和 Fish 实例来结合飞行和游泳。 它还可以实现自己的行为(quacking)。 这避免了多重继承。
委托的最佳实践
- 首选组合: 在类之间共享行为时,使用委托而不是继承。
- 使用标准委托: 利用 lazy、observable 和 vetoable 委托来实现常见模式。
- 考虑可读性: 自定义委托可能很强大,但如果过度使用,可能会降低代码清晰度。
- 记录行为: 清楚地记录代码中任何非平凡的委托行为。
- 注意性能: 一些委托(如 lazy)会增加少量的运行时开销,这在热点路径中可能很重要。
来源
本教程深入介绍了 Kotlin 的 by 关键字,展示了类委托和属性委托模式。 我们探讨了标准委托并创建了自定义委托。 正确使用委托可以使您的代码更灵活且更易于维护。
作者
列出 所有 Kotlin 教程。