ZetCode

Kotlin 按关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的委托模式使用 by 关键字实现。它允许组合而非继承和属性委托。本教程将通过实际例子深入探讨 by 关键字。

基本定义

Kotlin 中的 by 关键字支持委托。它有两个主要用途:类委托和属性委托。类委托允许您通过委托给另一个对象来实现接口。属性委托将属性访问委托给另一个对象。

类委托

类委托允许通过委托给另一个对象来实现接口。这对于装饰器模式非常有用,无需样板代码。

ClassDelegation.kt
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

PropertyDelegation.kt
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'
}

SimpleDelegatemessage 提供值。 by 关键字将属性连接到其委托。访问属性时,会调用委托的 getValue

延迟初始化

lazy 委托仅在首次访问时初始化属性。这对于可能不需要的昂贵的初始化很有用。

LazyDelegate.kt
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 委托允许观察属性更改。每当属性值更改时,它都会调用一个处理程序。

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 = "Jane" // Output: John -> Jane
}

每次 name 更改时,处理程序都会打印旧值和新值。 by 关键字将属性连接到可观察委托。这对于更改跟踪很有用。

可否决属性

Delegates.vetoable 委托允许拒绝属性更改。处理程序可以返回 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
    println(num.value) // Output: 42 (change rejected)
}

委托仅接受非负数。尝试设置 -1 会被拒绝,保留先前的值。 by 关键字启用了这种验证行为。

自定义属性委托

您可以通过实现属性运算符函数来创建自定义委托。这允许完全控制属性访问。

CustomDelegate.kt
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 的结构很有用。

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}, ${user.age}") // Output: John Doe, 30
}

User 类将属性委托给提供的 map 中的值。 by 关键字将每个属性连接到其对应的 map 条目。此模式经常与 JSON 反序列化一起使用。

委托的最佳实践

来源

Kotlin 委托文档

本教程深入介绍了 Kotlin 的 by 关键字,展示了类委托和属性委托模式。我们探索了标准委托并创建了自定义委托。委托是 Kotlin 中代码重用和关注点分离的强大工具。

作者

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

列出 所有 Kotlin 教程