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 教程。