ZetCode

Kotlin 委托关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的委托模式允许对象组合来实现代码复用。 by 关键字无需样板代码即可实现委托。本教程通过实际示例探讨属性委托和类委托。

基本定义

Kotlin 中的 by 关键字实现了委托模式。 它有两种形式:类委托和属性委托。 委托有助于在类之间共享行为时避免继承。

类委托

类委托允许通过委托给另一个对象来实现接口。 当您想要扩展功能而无需继承时,这非常有用。

ClassDelegation.kt
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 委托仅在首次访问时初始化属性。 这对于可能并非总是需要的高成本操作很有用。

LazyDelegate.kt
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 委托允许观察属性更改。 当属性更改时,它会接收旧值和新值。

ObservableDelegate.kt
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 以拒绝更改。

VetoableDelegate.kt
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 运算符函数来创建自定义委托。 这允许完全控制属性访问。

CustomDelegate.kt
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 解析)非常有用。

MapDelegate.kt
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 反序列化一起使用。

多接口委托

一个类可以将多个接口委托给不同的对象。 这允许组合来自多个来源的行为。

MultipleDelegation.kt
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)。 这避免了多重继承。

委托的最佳实践

来源

Kotlin 委托文档

本教程深入介绍了 Kotlin 的 by 关键字,展示了类委托和属性委托模式。 我们探讨了标准委托并创建了自定义委托。 正确使用委托可以使您的代码更灵活且更易于维护。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。 自 2007 年以来,我一直在撰写编程文章。到目前为止,我写了 1400 多篇文章和 8 本电子书。 我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程