Kotlin Delegation什么是委托?

3 阅读7分钟

不理解是怎么实现的

//Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val base = BaseImpl(10)
    Derived(base).print()//凭什么可以调用print(),就是by b
}

//Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。
//语言特性——多态,父类引用可以指向子类对象。


从两个层面来讲:

类的委托

当我们使用by b这样的语法时,编译器会自动做两件事:

  1. Derived类内部存储一个引用,指向b(也就是我们传入的Base实例)。
  2. 编译器会为Derived类生成Base接口中所有方法(这里就是print()方法)的实现,这些生成的实现会转发给存储的b对象。也就是说,生成的Derived类的print()方法会调用b.print()

如果没有这个语法糖,我们可能需要这样写:

class Derived(private val b: Base) : Base {
    override fun print() {
        b.print()
    }
}
//相当于重复的模版代码,

但是使用by关键字,我们就可以省略这些样板代码,编译器会自动生成类似上面的代码。

因此,by关键字是Kotlin提供的一种语法糖,它通过编译器自动生成代码来简化委托模式的实现。这样,我们就可以将接口的实现委托给另一个对象,而不需要手动编写转发方法。

为什么可以这样实现?

编译器的实现原理

  1. 编译时处理by 关键字告诉Kotlin编译器:

    • "这个类实现了Base接口"
    • "但具体实现请委托给参数b"
  2. 生成字节码:编译器会生成实际的Java字节码,包含:

// 反编译后的Java代码类似:
public final class Derived implements Base {
    private final Base delegate;
    
    public Derived(Base b) {
        this.delegate = b;
    }
    
    @Override
    public void print() {
        delegate.print();  // 自动生成的转发调用
    }
}

验证例子

// 可以验证编译器生成的内容:
interface Base {
    fun print()
    fun show()  // 添加第二个方法
}

class BaseImpl(val x: Int) : Base {
    override fun print() { println("Print: $x") }
    override fun show() { println("Show: $x") }
}

class Derived(b: Base) : Base by b  // 一句话搞定!

fun main() {
    val base = BaseImpl(10)
    val derived = Derived(base)
    
    derived.print()  // 输出: Print: 10
    derived.show()   // 输出: Show: 10
    // 两个方法都能正常工作,虽然Derived类中没有显式实现它们
}

这种设计的优势

1. 代码复用

// 多个类可以复用同一个实现
class Derived1(b: Base) : Base by b
class Derived2(b: Base) : Base by b
class Derived3(b: Base) : Base by b
// 无需重复编写相同的实现代码

2. 装饰器模式简化

class EnhancedDerived(b: Base) : Base by b {
    // 可以只重写部分方法
    override fun print() {
        println("Before print")
        b.print()
        println("After print")
    }
    // show()方法会自动委托给b,无需重写
}

3. 运行时动态性

fun main() {
    val impl1 = BaseImpl(10)
    val impl2 = BaseImpl(20)
    
    val derived = Derived(impl1)
    derived.print()  // 使用impl1
    
    // 虽然不能直接改变delegate,但可以创建新实例
    val derived2 = Derived(impl2)  // 使用不同的实现
}

底层原理总结

阶段发生什么
编写代码只用写 : Base by b
编译时编译器检测到 by 关键字
代码生成自动生成delegate字段和所有接口方法的转发实现
运行时调用Derived的方法 → 转发给存储的delegate对象

所以"凭什么可以这样实现"的答案就是:这是Kotlin编译器提供的一种语法糖,它在编译阶段自动帮我们生成了委托模式所需的样板代码。

这类似于Java中的注解处理器或Lombok,但它是Kotlin语言内置的特性,更直接地集成在编译流程中。

属性的委托

为什么会出现?

对于某些常见的属性,虽然每次需要时都可以手动实现,但更方便的做法是一次性实现,将其添加到库中,以便日后重复使用。例如:

  • 延迟属性(lazy properties): 其值只在首次访问时计算。
  • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知。
  • 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。

从官方语法说明看实现原理

委托属性(Delegated Property)

  • 作用于类的单个属性,将属性的getter和setter委托给另一个对象(委托提供者)。
  • 使用 by 关键字在属性声明时指定委托对象。

语法是:

  1. val/var <属性名>: <类型> by <表达式>

  2. 在 by 后面的表达式是该 委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。

  3. 属性的委托不必实现接口,但是需要提供一个 getValue() 函数(对于 var 属性还有 setValue())。

委托属性的实现机制

  1. 编译时:编译器看到 by 关键字,知道这是一个委托属性。
  2. 生成代码:编译器会生成一个委托对象实例,并将属性的getter和setter调用转发给该委托对象的 getValue 和 setValue 方法。
  3. 运行时:当访问属性时,实际上调用的是委托对象的方法。

Kotlin 标准库中的委托属性

Kotlin 标准库提供了几个实用的委托属性实现:

1. lazy - 延迟初始化

class LazyDemo {
    // 只有第一次访问时才计算值
    val lazyValue: String by lazy {
        println("计算中...")
        "结果值"
    }
}

fun main() {
    val demo = LazyDemo()
    println(demo.lazyValue)  // 输出: 计算中... 结果值
    println(demo.lazyValue)  // 输出: 结果值 (不重新计算)
}

2. observable - 可观察属性

import kotlin.properties.Delegates

class User {
    // 当属性值改变时,会触发回调
    var name: String by Delegates.observable("初始值") { 
        property, oldValue, newValue ->
        println("${property.name}$oldValue 变为 $newValue")
    }
}

fun main() {
    val user = User()
    user.name = "Alice"  // 输出: name 从 初始值 变为 Alice
    user.name = "Bob"    // 输出: name 从 Alice 变为 Bob
}

3. vetoable - 可否决的观察

import kotlin.properties.Delegates

class User {
    var age: Int by Delegates.vetoable(0) { 
        property, oldValue, newValue ->
        // 返回true接受新值,false拒绝
        newValue >= 0  // 只允许非负年龄
    }
}

fun main() {
    val user = User()
    user.age = 20  // 接受
    println(user.age)  // 20
    
    user.age = -5  // 拒绝(回调返回false)
    println(user.age)  // 仍然是20
}

4. 将属性存储在Map中

class User(val map: Map<String, Any?>) {
    // 属性值从map中读取
    val name: String by map
    val age: Int by map
}

fun main() {
    val user = User(mapOf(
        "name" to "John",
        "age" to 25
    ))
    
    println(user.name)  // "John"
    println(user.age)   // 25
}

委托属性的底层原理

编译器做了什么?

// 源代码:
class Example {
    var prop: Type by Delegate()
}

// 编译器生成的伪代码:
class Example {
    // 1. 创建委托实例
    private val prop$delegate = Delegate()
    
    // 2. 生成属性的getter
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

委托内必须实现的约定

// 对于只读属性(val):
class ReadOnlyDelegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 感谢你委托属性 '${property.name}' 给我!"
    }
}

// 对于读写属性(var):
class ReadWriteDelegate {
    private var storedValue: String = ""
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return storedValue
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        storedValue = value
    }
}

// 使用:
class Example {
    val readOnly: String by ReadOnlyDelegate()
    var readWrite: String by ReadWriteDelegate()
}

自定义委托属性的完整示例

import kotlin.reflect.KProperty

// 自定义委托:将属性值存储在SharedPreferences中
class PreferenceDelegate<T>(
    private val context: Context,
    private val key: String,
    private val defaultValue: T
) {
    private val prefs by lazy {
        context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
    }
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return when (defaultValue) {
            is String -> prefs.getString(key, defaultValue as String) as T
            is Int -> prefs.getInt(key, defaultValue as Int) as T
            is Boolean -> prefs.getBoolean(key, defaultValue as Boolean) as T
            else -> throw IllegalArgumentException("不支持的类型")
        }
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        with(prefs.edit()) {
            when (value) {
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
                else -> throw IllegalArgumentException("不支持的类型")
            }
            apply()
        }
    }
}

// 使用:
class SettingsActivity : AppCompatActivity() {
    // 自动保存到SharedPreferences
    var userName: String by PreferenceDelegate(this, "user_name", "Guest")
    var notificationsEnabled: Boolean by PreferenceDelegate(this, "notifications", true)
    var fontSize: Int by PreferenceDelegate(this, "font_size", 16)
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 读取(自动从SharedPreferences获取)
        println("用户名: $userName")
        
        // 写入(自动保存到SharedPreferences)
        userName = "Alice"
        fontSize = 18
    }
}

委托属性的高级用法

组合多个委托

// 自定义委托:先记录日志,再调用原始委托
class LoggingDelegate<T>(private val delegate: ReadWriteDelegate) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        println("获取属性: ${property.name}")
        return delegate.getValue(thisRef, property) as T
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        println("设置属性: ${property.name} = $value")
        delegate.setValue(thisRef, property, value)
    }
}

// 使用装饰器模式组合
class Example {
    private val basicDelegate = ReadWriteDelegate()
    var prop: String by LoggingDelegate(basicDelegate)
}

属性委托工厂

// 委托可以带参数,类似工厂模式
class ConfigurableDelegate(
    private val logChanges: Boolean = true,
    private val validate: (Any?) -> Boolean = { true }
) {
    private var value: Any? = null
    
    operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T {
        @Suppress("UNCHECKED_CAST")
        return value as T
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Any?) {
        if (!validate(value)) {
            throw IllegalArgumentException("无效的值: $value")
        }
        
        if (logChanges) {
            println("${property.name} 变为: $value")
        }
        
        this.value = value
    }
}

class User {
    // 可配置的委托
    var name: String by ConfigurableDelegate(
        logChanges = true,
        validate = { it is String && it.length >= 2 }
    )
    
    var age: Int by ConfigurableDelegate(
        logChanges = false,
        validate = { it is Int && it in 0..150 }
    )
}

总结

委托属性类委托的主要不同:

  1. 粒度不同

    • 类委托:整个接口/类的实现
    • 属性委托:单个属性的访问逻辑
  2. 实现机制不同

    • 类委托:编译器为每个接口方法生成转发调用
    • 属性委托:编译器生成 getValue()/setValue() 调用
  3. 典型用途不同

    • 类委托:装饰器模式、代码复用
    • 属性委托:延迟加载、属性观察、配置管理、数据绑定
  4. 灵活性不同

    • 属性委托更灵活,可以组合、配置、动态创建

委托属性是 Kotlin 中非常强大的特性,它让属性的访问逻辑可以像插件一样"插入",大大提高了代码的可复用性和可维护性。