【Android进阶】kotlin 委托

本文介绍了Kotlin中的委托模式,包括类委托和属性委托的概念及使用方法。文章详细解释了如何利用Kotlin的by关键字实现委托,并展示了不同类型的属性委托案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。

// 创建接口
interface Base {   
    fun print()
}

// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

属性委托语法格式:

val/var <属性名>: <类型> by <表达式>
  • var/val:属性类型(可变/只读)
  • 属性名:属性名称
  • 类型:属性的数据类型
  • 表达式:委托代理类
    by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。
定义一个被委托的类

该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。

import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example {
    var p: String by Delegate()
}

// 委托的类
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 这里委托了 ${property.name} 属性"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef 的 ${property.name} 属性赋值为 $value")
    }
}
fun main(args: Array<String>) {
    val e = Example()
    println(e.p)     // 访问该属性,调用 getValue() 函数

    e.p = "Runoob"   // 调用 setValue() 函数
    println(e.p)
}

输出结果为:

Example@433c675d, 这里委托了 p 属性
Example@433c675d 的 p 属性赋值为 Runoob
Example@433c675d, 这里委托了 p 属性

标准委托

Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。

延迟属性 Lazy

lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    println("computed!")     // 第一次调用输出,第二次调用不执行
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println(lazyValue)   // 第二次执行,只输出返回值
}

执行输出结果:

computed!
Hello
Hello

可观察属性 Observable

observable 可以用于实现观察者模式。

Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。

在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:

import kotlin.properties.Delegates

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 = "第二次赋值"
}

执行输出结果:

旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值

把属性储存在映射中

一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他"动态"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

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

fun main(args: Array<String>) {
    // 构造函数接受一个映射参数
    val site = Site(mapOf(
        "name" to "菜鸟教程",
        "url"  to "www.runoob.com"
    ))
    
    // 读取映射值
    println(site.name)
    println(site.url)
}

执行输出结果:

菜鸟教程
www.runoob.com

如果使用 var 属性,需要把 Map 换成 MutableMap:

class Site(val map: MutableMap<String, Any?>) {
    val name: String by map
    val url: String by map
}

fun main(args: Array<String>) {

    var map:MutableMap<String, Any?> = mutableMapOf(
            "name" to "菜鸟教程",
            "url" to "www.runoob.com"
    )

    val site = Site(map)

    println(site.name)
    println(site.url)

    println("--------------")
    map.put("name", "Google")
    map.put("url", "www.google.com")

    println(site.name)
    println(site.url)

}

执行输出结果:

菜鸟教程
www.runoob.com
--------------
Google
www.google.com

Not Null

notNull 适用于那些无法在初始化阶段就确定属性值的场合。

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar)

需要注意,如果属性在赋值前就被访问的话则会抛出异常。

局部委托属性

你可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。

属性委托要求

对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:

  • thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型
    这个函数必须返回与属性相同的类型(或其子类型)。

对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:

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

翻译规则

在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。 例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性:

class C {
    var prop: Type by MyDelegate()
}

// 这段是由编译器生成的相应代码:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}

Kotlin 编译器在参数中提供了关于 prop 的所有必要信息:第一个参数 this 引用到外部类 C 的实例而 this::prop 是 KProperty 类型的反射对象,该对象描述 prop 自身。

提供委托

通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来 创建属性委托实例。

provideDelegate 的一个可能的使用场景是在创建属性时(而不仅在其 getter 或 setter 中)检查属性一致性。

例如,如果要在绑定之前检查属性名称,可以这样写:

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // 创建委托
    }

    private fun checkProperty(thisRef: MyUI, name: String) { …… }
}

fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… }

class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}

provideDelegate 的参数与 getValue 相同:

  • thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
  • property —— 必须是类型 KProperty<*> 或其超类型。
    在创建 MyUI 实例期间,为每个属性调用 provideDelegate 方法,并立即执行必要的验证。

如果没有这种拦截属性与其委托之间的绑定的能力,为了实现相同的功能, 你必须显式传递属性名,这不是很方便:

// 检查属性名称而不使用“provideDelegate”功能
class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")
}

fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
   checkProperty(this, propertyName)
   // 创建委托
}

在生成的代码中,会调用 provideDelegate 方法来初始化辅助的 prop$delegate 属性。 比较对于属性声明 val prop: Type by MyDelegate() 生成的代码与 上面(当 provideDelegate 方法不存在时)生成的代码:

class C {
    var prop: Type by MyDelegate()
}

// 这段代码是当“provideDelegate”功能可用时
// 由编译器生成的代码:
class C {
    // 调用“provideDelegate”来创建额外的“delegate”属性
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)
}

请注意,provideDelegate 方法只影响辅助属性的创建,并不会影响为 getter 或 setter 生成的代码。

<think>嗯,用户这次问的是Kotlin委托属性的优势和应用场景。结合之前提供的四篇站内引用,需要从多个角度展开分析。用户可能是个Android开发者,或者正在学习Kotlin的中高级特性,想了解如何用委托优化实际项目。注意到引用[1][2]提到KotlinAndroid和跨平台开发中的优势,引用[3][4]则直接展示了属性委托的代码实现和库应用。特别要强调MMKV-KTX这个案例(引用[4]),它把委托属性的“透明数据存取”优势体现得很直观——开发者完全不用关心SharedPreferences的commit操作,用属性赋值就能自动持久化。关于优势部分,应该重点突出三点:代码复用(DRY原则)、封装能力(如懒加载的线程安全)、声明式编程(引用[2]提到的DSL特性)。那个lazy的线程安全例子特别有说服力,用户可能自己踩过非同步初始化的坑。另外引用[3]的标准库接口ReadOnlyProperty也值得展开,说明Kotlin官方是如何支持这特性的。应用场景方面,除了基础的惰性加载和观察者,要重点讲Android方向(毕竟用户提到引用[4])。比如用委托简化ViewBinding、LiveData转换,这些都能极大减少样板代码。突然想到还可以提Koin这类DI框架——它们底层其实也用了属性委托来实现依赖注入。用户的问题质量不错,聚焦在具体特性的实践价值上。可能需要提醒的是委托虽好但别滥用,尤其要注意委托对象的初始化时机问题。相关问题的设计可以引导到原理层面(如编译后字节码结构)和进阶用法(如自定义委托优化SharedPreferences)。</think> Kotlin 委托属性通过`by`关键字将属性的**访问逻辑委托给外部对象**,其核心优势在于**解耦属性操作与宿主类**,提升代码的简洁性、可读性和可维护性。结合站内引用,具体优势及应用场景如下: --- ### **核心优势** 1. **消除样板代码 (Boilerplate Reduction)** 将重复逻辑(如数据校验、存储、懒加载)封装在委托对象中,避免在每个属性中重复编写。 *示例:* `val/var <属性名> by <委托对象>` 2. **逻辑复用 (Logic Reusability)** 同一委托对象可被多个属性复用(如统一的数据存储规则)。 3. **延迟初始化 (Lazy Initialization)** 通过 `lazy` 委托实现属性按需初始化,节省资源且线程安全: ```kotlin val heavyResource by lazy { /* 耗时初始化逻辑 */ } ``` 4. **属性监听 (Observable Properties)** 使用 `Delegates.observable` 或 `vetoable` 监听属性变化: ```kotlin var name by Delegates.observable("") { _, old, new -> println("$old → $new") } ``` 5. **简化框架集成 (Framework Integration)** 如 Android 中 MMKV-KTX 通过委托简化数据存储(引用[^4]): ```kotlin // 无需手动调用 put/get var settings: String by mmkvPrefs("key", "default") ``` 6. **类型安全委托 (Type-Safe Delegation)** 通过 `ReadOnlyProperty`/`ReadWriteProperty` 接口(引用[^3])规范委托实现: ```kotlin val name by object : ReadOnlyProperty<Any?, String> { override fun getValue(thisRef: Any?, property: KProperty<*>) = "Peng" } ``` --- ### **典型应用场景** 1. **Android 数据存储** 结合 SharedPreferences/MMKV 等,以委托方式读写数据: ```kotlin // 引用[^4]:MMKV-KTX 实现 class UserPrefs(mmkv: MMKV) { var token by mmkv.string("token", defaultValue = "") } ``` 2. **View 绑定与资源获取** 简化 View 初始化与资源加载: ```kotlin // 委托实现 Fragment 的 ViewBinding private val binding by viewBinding(FragmentLayout::bind) ``` 3. **依赖注入 (DI)** 结合 Koin 等框架,委托实现服务的懒加载注入: ```kotlin val userService by inject<UserService>() ``` 4. **配置管理** 统一管理应用配置项的读取和更新: ```kotlin object AppConfig { var apiUrl by Delegates.observable("") { _, _, new -> updateApiClient(new) } } ``` 5. **数据校验** 通过自定义委托实现自动校验: ```kotlin var email by Validator("") { it.contains("@") } ``` --- ### **与 Java 对比的优势** | **场景** | **Java 实现** | **Kotlin 委托属性** | |-------------------|-----------------------------------|----------------------------| | 属性监听 | 手动实现 setter 逻辑 | `Delegates.observable` | | 懒加载 | 双重检查锁 + volatile 变量 | `by lazy { ... }` | | 数据存储 | 手动调用 SharedPreferences 读写 | 委托自动封装 | | 类型安全 | 需自定义接口 | 标准库提供 `ReadOnlyProperty` | --- ### **总结** Kotlin 委托属性通过**解耦属性访问逻辑**,显著提升了代码的: 1️⃣ **简洁性**(减少重复代码) 2️⃣ **扩展性**(逻辑复用与组合) 3️⃣ **可维护性**(统一修改委托实现) 4️⃣ **安全性**(类型安全接口支持) 其应用在 Android 开发(如数据存储、View 绑定)、服务端配置管理、DSL 设计等领域已成为最佳实践(引用[^2][^4])。 --- **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值