Kotlin | 委托机制 & 原理

点赞关注,不再迷路,你的支持对我意义重大!

? Hi,我是丑丑。本文 「Android 路线」| 导读 —— 从零到无穷大 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)


前言

  • 委托(Delegate)是 Kotlin 的一种语言特性,用于更加优雅地实现委托模式;
  • 在这篇文章里,我将总结 Kotlin 委托机制的使用方法 & 原理,如果能帮上忙,请务必点赞加关注,这真的对我非常重要。

目录


1. 概述

  • 什么是委托: 一个对象将消息委托给另一个对象来处理。
  • Kotlin 委托解决了什么问题: Kotlin 通过 by 关键字可以更加优雅地实现委托。

2. Kotlin 委托基础

  • 类委托: 一个类的方法不在该类中定义,而是直接委托给另一个对象来处理。
  • 属性委托: 一个类的属性不在该类中定义,而是直接委托给另一个对象来处理。
  • 局部变量委托: 一个局部变量不在该方法中定义,而是直接委托给另一个对象来处理。

2.1 类委托

Kotlin 类委托的语法格式如下:

class <类名>(b : <基础接口>) : <基础接口> by <基础对象>
复制代码

举例:

// 基础接口
interface Base {   
    fun print()
}

// 基础对象
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// 被委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 最终调用了 Base#print()
}
复制代码

基础类和被委托类都实现同一个接口,编译时生成的字节码中,继承自 Base 接口的方法都会委托给基础对象处理。

2.2 属性委托

Kotlin 属性委托的语法格式如下:

val/var <属性名> : <类型> by <基础对象>
复制代码

举例:

class Example {
    // 被委托属性
    var prop: String by Delegate() // 基础对象
}

// 基础类
class Delegate {
    private var _realValue: String = "彭"

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("getValue")
        return _realValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("setValue")
        _realValue = value
    }
}

fun main(args: Array<String>) {
    val e = Example()
    println(e.prop)    // 最终调用 Delegate#getValue()
    e.prop = "Peng"    // 最终调用 Delegate#setValue()
    println(e.prop)    // 最终调用 Delegate#getValue()
}

输出:
getValue
彭
setValue
getValue
Peng
复制代码

基础类不需要实现任何接口,但必须提供 getValue() 方法,如果是委托可变属性,还需要提供 setValue()。在每个属性委托的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,会生成「辅助属性」 prop$delegate。 而 prop 的 getter() 和 setter() 方法只是简单地委托给辅助属性的 getValue() 和 setValue() 处理。

源码:
class Example {
    // 被委托属性
    var prop: String by Delegate() // 基础对象
}

--------------------------------------------------------
编译器生成的字节码:
class Example {
    private val prop$delegate = Delegate()
    // 被委托属性
    var prop: String
        get() = prop$delegate.getValue(this, this:prop)
        set(value : String) = prop$delegate.setValue(this, this:prop, value)
}
复制代码

注意事项:

  • thisRef —— 必须与属性所有者类型相同或者是它的超类型。
  • property —— 必须是类型 KProperty<*> 或其超类型。
  • value —— 必须和属性同类型或者是它的超类型。

2.3 局部变量委托

局部变量也可以声明委托,例如:

fun main(args: Array<String>) {
    val lazyValue: String by lazy {
        println("Lazy Init Completed!")
        "Hello World."
    }

    if (true/*someCondition*/) {
        println(lazyValue) // 首次调用
        println(lazyValue) // 后续调用

    }
}
输出:
Lazy Init Completed!
Hello World.
Hello World.
复制代码

3. Kotlin 委托进阶

3.1 延迟属性委托 lazy

lazy 是一个标准库函数,参数为一个 Lambda 表达式,返回值为一个 Lazy 实例,使用 lazy 可以实现延迟属性委托,在委托对象比较耗资源的场景会非常有用。首次访问属性是,会执行 lazy 函数的 lambda 表达式并将结果记录到「背域」,后续调用 getter() 方法只是直接返回「背域」的值。 例如:

val lazyValue: String by lazy {
    println("Lazy Init Completed!")
    "Hello World."
}

fun main(args: Array<String>) {
    println(lazyValue) // 首次调用
    println(lazyValue) // 后续调用
}

输出:
Lazy Init Completed!
Hello World.
Hello World.
复制代码

3.2 可观察属性 ObservableProperty

使用 Delegates.observable() 可以实现可观察属性,函数接受两个参数:第一个参数为初始值,第二个参数为属性值变化的回调。函数的返回值是 ObservableProperty 可观察属性,它在调用 setValue(…) 是触发回调。例如:

class User {
    var name: String by Delegates.observable("初始值") { prop, old, new ->
        println("旧值:$old -> 新值:$new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "第一次赋值"
    user.name = "第二次赋值"
}

输出:
旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值
复制代码

3.3 使用 Map 存储属性值

Map / MutableMap 也可以用来实现属性委托,从而此时字段名是 Key,属性值是 Value。例如;

class User(val map: Map<String, Any?>) {
    val name: String by map
}

fun main(args: Array<String>) {
    val map = mutableMapOf(
        "name" to "彭"
    )
    val user = User(map)
    println(user.name)
    map["name"] = "peng"
    println(user.name)
}
输出:
彭
peng
复制代码

4. 总结

Kotlin 委托的语法关键字是 by,其本质上是面向编译器的语法糖,三种委托(类委托、对象委托和局部变量委托)在编译时都会转化为 “无糖语法”。例如类委托:编译器会实现基础接口的所有方法,并直接委托给基础对象来处理。例如对象委托和局部变量委托:在编译时会生成辅助属性(prop$degelate),而属性 / 变量的 getter() 和 setter() 方法只是简单地委托给辅助属性的 getValue() 和 setValue() 处理。


参考资料

  • Kotlin 委托 —— 菜鸟教程 著
  • Kotlin 实战(第 7 章)—— [俄] DmitryJeme 著

创作不易,你的「三连」是丑丑最大的动力,我们下次见!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享