Kotlin教程笔记(17) - 属性代理

简介: Kotlin教程笔记(17) - 属性代理

本系列学习教程笔记属于详细讲解Kotlin语法的教程,需要快速学习Kotlin语法的小伙伴可以查看“简洁” 系列的教程

快速入门请阅读如下简洁教程:
Kotlin学习教程(一)
Kotlin学习教程(二)
Kotlin学习教程(三)
Kotlin学习教程(四)
Kotlin学习教程(五)
Kotlin学习教程(六)
Kotlin学习教程(七)
Kotlin学习教程(八)
Kotlin学习教程(九)
Kotlin学习教程(十)

Kotlin教程笔记(17) - 属性代理

imgKotlin - 属性代理

#自定义属性代理

代理也叫委托,Kotlin 支持属性代理,把一个属性的获取与赋值交给一个“中介”(下称 Delegate)去管理,属性代理的语法是: val/var <属性名>: <类型> by <表达式> ,by 后面是 Delegate 对象,被代理属性的 get()set() 会给 Delegate 对象对应的 getValue()setValue() 分别代理,因此 Delegete 可以这么写:

注意:Delegate 并不需要实现任何接口,仅需提供 getValue()setValue() 即可。

class Delegate {
    private var _value: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("getValue() thisRef=$thisRef, property=${property.name}")
        return _value ?: ""
    }

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

该 Delegate 的 getValue()setValue() 方法对自己的 _value 属性进行获取与赋值,并分别在这 2 个方法中打印输出各参数值,下面我们将一个类成员属性交给 Delegate 对象代理:

class Animal {
    var name: String by Delegate()
}
fun main() {
    val animal = Animal()
    animal.name = "旺财" // setValue() thisRef=com.charylin.kotlinlearn.Animal@1b28cdfa, property=name, value=旺财
    println(animal.name) // getValue() thisRef=com.charylin.kotlinlearn.Animal@1b28cdfa, property=name
                       // 旺财
}

当代理属性被赋值与访问时,就会输出 Delegate 对象 setValue()getValue() 中的日志,从输出的日志可以看出,thisRef 是代理属性的实例对象,property 是代理属性的包装。可见,属性代理相比单纯的属性操作更加强大,在某些场景下,属性值的访问会比较复杂,如文件或 Redis,属性代理可以将文件或 Redis 的操作全部交给 Delegate 完成,而业务代码不需要知道具体实现,这对代码解耦与简化很有帮助。

#延迟属性 lazy

Kotlin 官方提供了 lazy 代理,可以延迟初始化 val 常量,且只会初始化一次:

class Animal {
    val age: Int by lazy {
        println("设置age")
        18 // lambda表达式会把最后语句的执行结果作为返回值
    }
}
fun main() {
    val animal = Animal()
    println(animal.age) // 设置age
                        // 18
    println(animal.age) // 18
}

可以看到 println("设置age") 只被输出了一次,这是怎么办到的呢?按住 ctrl +鼠标左键,点击 lazy 查看源代码:

// LazyJVM.kt
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

lazy() 函数需要传入一个 lambda 表达式,返回值是 Lazy<T>,返回结果 SynchronizedLazyImpl(initializer) 就是 Lazy<T> 的实现类对象,我们知道 by 后面接的是 Delegate 对象,lazy() 函数返回的 SynchronizedLazyImpl 就是真正的 Delegate ,再来看看 LazySynchronizedLazyImpl 的源代码:

// Lazy.kt
public interface Lazy<out T> {
    public val value: T
    ...
}

// LazyJVM.kt
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    ...

    override val value: T
        get() {
            val _v1 = _value
            if (_v1 !== UNINITIALIZED_VALUE) {
                // 已经初始化过了,直接返回
                return _v1 as T
            }

            return synchronized(lock) {
                ...
                // 执行initializer()初始化_value值
                val typedValue = initializer!!()
                _value = typedValue
                initializer = null
            }
        }
    ...
}

SynchronizedLazyImpl 重写了 value 属性的 getter 方法,其中会判断 value 是否有初始化过,已经初始化就直接返回结果,未初始化才执行 initializer,所以,这就是 by lazy{} 的初始化逻辑只会执行一次的原因。前面说过,Delegate 对象需要有 getValue()setValue() 方法,那么 Lazy 的这 2 个方法在哪呢?按住 ctrl +鼠标左键,点击 by 可以找到:

// Lazy.kt
@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

发现,Lazy 只有 getValue(),没有 setValue(),这不难理解,var 变量延迟初始化使用的是 lateinit,val 常量使用的 by lazy,val 常量不可修改,所以不需要提供 setValue()

#可观察属性 Observable

除了 by lazy 外,Kotlin 为还提供了可观察属性 Observable,这个属性代理可以监听属性值的变化,需要通过 by Delegates.observable() 来设置,这是 Delegates.observable() 的源码,具体实现就不深入了,该函数要求传入 2 个参数,分别是初始值 initialValue 和用于监听值变化的 lambda 表达式:

// Delegates.kt
public object Delegates {
    /**
    * Returns a property delegate for a read/write property that calls a specified callback function when changed.
    * @param initialValue the initial value of the property.
    * @param onChange the callback which is called after the change of the property is made. The value of the property
    *  has already been changed when this callback is invoked.
    *
    *  @sample samples.properties.Delegates.observableDelegate
    */
    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }
    ...
}

下面我们来使用 Delegates.observable() 监听属性值的变化:

class Animal {
    var gender: Boolean by Delegates.observable(true) { prop, old, new ->
        println("prop = ${prop.name}, old = $old, new = $new")
    }
}

fun main() {
    val animal = Animal()
    println(animal.gender) // true
    animal.gender = false // prop = gender, old = true, new = false
    println(animal.gender) // false
}

可以看到,当修改了属性值时,会触发 Delegates.observable() 参数 2 的 lambda 表达式,输出了相应的日志信息,随后属性值也发生了改变。Kotlin 还提供了 Delegates.vetoable() 可观察属性代理,vetoableobservable 要强大一点,它除了能监听属性值变化,还可以控制属性是否要修改,通过查看 Delegates.vetoable() 源码注释可以知道,其参数 2 的 lambda 表达式(也就是 callback)返回值将决定属性值是否被更新:

// Delegates.kt
public object Delegates {
    /**
     * Returns a property delegate for a read/write property that calls a specified callback function when changed,
     * allowing the callback to veto the modification.
     * @param initialValue the initial value of the property.
     * @param onChange the callback which is called before a change to the property value is attempted.
     *  The value of the property hasn't been changed yet, when this callback is invoked.
     *  If the callback returns `true` the value of the property is being set to the new value,
     *  and if the callback returns `false` the new value is discarded and the property remains its old value.
     *
     *  @sample samples.properties.Delegates.vetoableDelegate
     *  @sample samples.properties.Delegates.throwVetoableDelegate
     */
    public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
        }
    ...
}

我们将上面例子中的 gender 属性代理修改成 Delegates.vetoable(),并在 callback 中返回 false,以阻止属性值的修改:

class Animal {
    var gender: Boolean by Delegates.vetoable(true) { prop, old, new ->
        println("prop = ${prop.name}, old = $old, new = $new")
        false // lambda表达式最后语句的结果作为返回值
    }
}

fun main() {
    val animal = Animal()
    println(animal.gender) // true
    animal.gender = false // prop = gender, old = true, new = false
    println(animal.gender) // true
}

至此属性代理的原理及常用属性代理就讲解完了,如需了解更多属性代理的应用,请点击:https://www.kotlincn.net/docs/reference/delegated-properties.html

相关文章
|
5天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。
|
21天前
|
存储 人工智能 弹性计算
阿里云弹性计算_加速计算专场精华概览 | 2024云栖大会回顾
2024年9月19-21日,2024云栖大会在杭州云栖小镇举行,阿里云智能集团资深技术专家、异构计算产品技术负责人王超等多位产品、技术专家,共同带来了题为《AI Infra的前沿技术与应用实践》的专场session。本次专场重点介绍了阿里云AI Infra 产品架构与技术能力,及用户如何使用阿里云灵骏产品进行AI大模型开发、训练和应用。围绕当下大模型训练和推理的技术难点,专家们分享了如何在阿里云上实现稳定、高效、经济的大模型训练,并通过多个客户案例展示了云上大模型训练的显著优势。
|
25天前
|
存储 人工智能 调度
阿里云吴结生:高性能计算持续创新,响应数据+AI时代的多元化负载需求
在数字化转型的大潮中,每家公司都在积极探索如何利用数据驱动业务增长,而AI技术的快速发展更是加速了这一进程。
|
16天前
|
并行计算 前端开发 物联网
全网首发!真·从0到1!万字长文带你入门Qwen2.5-Coder——介绍、体验、本地部署及简单微调
2024年11月12日,阿里云通义大模型团队正式开源通义千问代码模型全系列,包括6款Qwen2.5-Coder模型,每个规模包含Base和Instruct两个版本。其中32B尺寸的旗舰代码模型在多项基准评测中取得开源最佳成绩,成为全球最强开源代码模型,多项关键能力超越GPT-4o。Qwen2.5-Coder具备强大、多样和实用等优点,通过持续训练,结合源代码、文本代码混合数据及合成数据,显著提升了代码生成、推理和修复等核心任务的性能。此外,该模型还支持多种编程语言,并在人类偏好对齐方面表现出色。本文为周周的奇妙编程原创,阿里云社区首发,未经同意不得转载。
11596 12
|
10天前
|
人工智能 自然语言处理 前端开发
100个降噪蓝牙耳机免费领,用通义灵码从 0 开始打造一个完整APP
打开手机,录制下你完成的代码效果,发布到你的社交媒体,前 100 个@玺哥超Carry、@通义灵码的粉丝,可以免费获得一个降噪蓝牙耳机。
4091 14
|
17天前
|
人工智能 自然语言处理 前端开发
用通义灵码,从 0 开始打造一个完整APP,无需编程经验就可以完成
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。本教程完全免费,而且为大家准备了 100 个降噪蓝牙耳机,送给前 100 个完成的粉丝。获奖的方式非常简单,只要你跟着教程完成第一课的内容就能获得。
6847 10
|
29天前
|
缓存 监控 Linux
Python 实时获取Linux服务器信息
Python 实时获取Linux服务器信息
|
14天前
|
人工智能 自然语言处理 前端开发
什么?!通义千问也可以在线开发应用了?!
阿里巴巴推出的通义千问,是一个超大规模语言模型,旨在高效处理信息和生成创意内容。它不仅能在创意文案、办公助理、学习助手等领域提供丰富交互体验,还支持定制化解决方案。近日,通义千问推出代码模式,基于Qwen2.5-Coder模型,用户即使不懂编程也能用自然语言生成应用,如个人简历、2048小游戏等。该模式通过预置模板和灵活的自定义选项,极大简化了应用开发过程,助力用户快速实现创意。
|
3天前
|
机器学习/深度学习 人工智能 安全
通义千问开源的QwQ模型,一个会思考的AI,百炼邀您第一时间体验
Qwen团队推出新成员QwQ-32B-Preview,专注于增强AI推理能力。通过深入探索和试验,该模型在数学和编程领域展现了卓越的理解力,但仍在学习和完善中。目前,QwQ-32B-Preview已上线阿里云百炼平台,提供免费体验。
|
11天前
|
人工智能 C++ iOS开发
ollama + qwen2.5-coder + VS Code + Continue 实现本地AI 辅助写代码
本文介绍在Apple M4 MacOS环境下搭建Ollama和qwen2.5-coder模型的过程。首先通过官网或Brew安装Ollama,然后下载qwen2.5-coder模型,可通过终端命令`ollama run qwen2.5-coder`启动模型进行测试。最后,在VS Code中安装Continue插件,并配置qwen2.5-coder模型用于代码开发辅助。
755 5