文章目录

Kotlin 委托:by 关键字与委托属性

发布于 2026-04-03 02:21:12 · 浏览 6 次 · 评论 0 条

Kotlin 委托:by 关键字与委托属性

在 Kotlin 中,委托是一种设计模式,允许一个对象(委托者)将某些操作“转交”给另一个对象(被委托者)处理。这种机制通过 by 关键字实现,能显著减少样板代码,提升代码复用性和可维护性。委托分为两类:类委托(Class Delegation)和委托属性(Delegated Properties)。本文将手把手教你如何使用它们。


使用类委托简化接口实现

当你需要实现一个接口,但大部分逻辑与其他已有类相同,可以用类委托避免重复编写代码。

  1. 定义一个接口,例如 Printer

    interface Printer {
     fun print(message: String)
    }
  2. 创建一个已实现该接口的类,比如 ConsolePrinter

    class ConsolePrinter : Printer {
     override fun print(message: String) {
         println("Console: $message")
        }
    }
    ```
    
    3. **使用 `by` 关键字委托实现**。新建一个类 `LoggedPrinter`,它不自己实现 `print` 方法,而是把调用交给内部的 `ConsolePrinter` 实例:
    ```kotlin
    class LoggedPrinter(printer: Printer) : Printer by printer
    ```
    
    4. **调用时自动转发**。当你调用 `LoggedPrinter` 的 `print` 方法,实际执行的是 `ConsolePrinter` 的实现:
    ```kotlin
    val printer = LoggedPrinter(ConsolePrinter())
    printer.print("Hello") // 输出:Console: Hello
    ```
    
    **关键点**:`by` 后面跟的是一个**已存在的对象实例**,Kotlin 编译器会自动生成接口所有方法的转发代码,你无需手动重写。
    
    ---
    
    ### 使用委托属性管理变量行为
    
    委托属性用于将属性的 getter/setter 逻辑抽离到独立的类中。常见场景包括延迟初始化、监听属性变化、映射到 Map 等。
    
    #### 场景一:延迟初始化(lazy)
    
    当你有一个耗资源的对象,希望首次访问时才创建:
    
    1. **使用 `by lazy` 声明属性**:
    ```kotlin
    val expensiveObject: String by lazy {
        println("Initializing...")
        "Computed Value"
    }
    ```
    
    2. **首次读取时触发初始化**:
    ```kotlin
    println(expensiveObject) // 输出:Initializing... 和 Computed Value
    println(expensiveObject) // 仅输出:Computed Value(后续不再初始化)
    ```
    
    **注意**:`lazy` 是线程安全的默认实现,适用于单例或只读场景。
    
    #### 场景二:监听属性变化(observable)
    
    当你需要在属性值改变时执行回调:
    
    1. **用 `Delegates.observable` 声明属性**,并提供初始值和监听函数:
    ```kotlin
    import kotlin.properties.Delegates
    
    var name: String by Delegates.observable("Alice") { prop, old, new ->
        println("$prop changed from $old to $new")
    }
  3. 赋值时自动触发回调

    name = "Bob" // 输出:property name changed from Alice to Bob

回调参数说明

  • prop:被观察的属性引用(类型为 KProperty<*>
  • old:旧值
  • new:新值

场景三:将属性存储到 Map(map-based delegation)

当你有一组动态属性,想用 Map 统一管理(如解析 JSON 或配置):

  1. 准备一个 MutableMap 容器

    val userConfig = mutableMapOf<String, Any?>(
     "username" to "john_doe",
     "age" to 30
    )
  2. 声明委托属性,绑定到 Map 的 key

    var username: String by userConfig
    var age: Int by userConfig
  3. 读写属性即操作 Map

    println(username) // 输出:john_doe
    age = 31
    println(userConfig["age"]) // 输出:31

要求:Map 的 key 必须与属性名一致,且泛型兼容。


自定义委托属性

Kotlin 允许你编写自己的委托逻辑。只需实现 getValuesetValue(针对 var)函数。

  1. 创建一个委托类,例如实现一个带范围检查的整数属性:

    class RangedInt(private val min: Int, private val max: Int) {
     private var value = min
    
     operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
         return value
     }
    
     operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
         if (newValue !in min..max) {
             throw IllegalArgumentException("Value out of range [$min, $max]")
         }
         value = newValue
     }
    }
  2. 在类中使用该委托

    class Thermostat {
     var temperature: Int by RangedInt(15, 30)
    }
  3. 测试边界行为

    val thermo = Thermostat()
    thermo.temperature = 25 // 正常
    thermo.temperature = 40 // 抛出异常:Value out of range [15, 30]

规则

  • 对于 val 属性,只需提供 getValue
  • 对于 var 属性,必须同时提供 getValuesetValue
  • 函数必须标记为 operator

常见内置委托工具汇总

Kotlin 标准库提供了多个实用委托,无需额外依赖:

委托函数 用途 适用属性类型
lazy() 延迟初始化,线程安全 val
Delegates.observable() 监听值变化 var
Delegates.vetoable() 可阻止赋值的监听器 var
Delegates.notNull() 非空延迟初始化(需手动赋值) var
Map 实例 将属性映射到 Map 的 key var / val

使用 Delegates.notNull() 示例:

class Person {
    var name: String by Delegates.notNull()
}
// 必须在使用前赋值,否则读取时报错

注意事项与最佳实践

  1. 避免过度使用:委托虽简洁,但会增加间接层。仅在逻辑复用明显时使用。
  2. 委托对象生命周期:确保被委托的对象在属性访问期间有效,避免内存泄漏。
  3. 线程安全lazy 默认是线程安全的(LazyThreadSafetyMode.SYNCHRONIZED),若在单线程环境可改用 LazyThreadSafetyMode.NONE 提升性能:
    val cache by lazy(LazyThreadSafetyMode.NONE) { createCache() }
  4. 委托属性不能是抽象的:你不能在接口中声明委托属性,因为委托逻辑依赖具体实现。

通过合理使用 by 关键字和委托属性,你可以写出更简洁、更易维护的 Kotlin 代码,将通用逻辑集中管理,让业务代码聚焦核心功能。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文