Kotlin的属性代理你真的理解了吗

本文深入探讨Kotlin的属性代理,揭示其背后的原理和源码。从基本定义、常见属性代理的使用(如Delegates.notNull(), Delegates.observable(), Delegates.vetoable())到自定义属性代理的实现,全方位解析这一强大的功能。通过对Kotlin标准库源码的分析,展示了属性代理如何在setter和getter中发挥作用,以及其与Java的对比。最后,鼓励读者透过语法糖理解其本质,提升开发效率。" 32920313,2511827,C++实现的工资管理系统,"['C++编程', '课程设计', '工资管理', '数据库操作', '软件工程']
摘要由CSDN通过智能技术生成

简述:
今天继续Kotlin原创系列的第十一讲,一起来揭开Kotlin属性代理的漂亮外衣。属性代理可以说是Kotlin独有的强大的功能之一,特别是对于框架开发的小伙伴来说非常有用,因为会经常涉及到更改存储和修改属性的方式操作,例如Kotlin中的SQL框架Exposed源码就大量使用了属性代理。相信你已经在代码也使用了诸如Delegates.observable()、Delegates.notNull()、Delegates.vetoable()或者自定义的属性代理。也许你还停留用的阶段或者对它还有点陌生,不用担心这篇文章将会基本上解决你所有的疑惑。废话不多说,直接来看一波章节导图:

一、属性代理的基本定义

  • 1、基本定义

属性代理是借助于代理设计模式,把这个模式应用于一个属性时,它可以将访问器的逻辑代理给一个辅助对象。

可以简单理解为属性的setter、getter访问器内部实现是交给一个代理对象来实现,相当于使用一个代理对象来替换了原来简单属性字段读写过程,而暴露外部属性操作还是不变的,照样是属性赋值和读取,只是setter、getter内部具体实现变了。

  • 2、基本语法格式
class Student{
   
    var name: String by Delegate()
}

class Delegate{
   
    operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T{
   
        ...
    }
    operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T){
   
        ...
    }
}

属性name将它访问器的逻辑委托给了Delegate对象,通过by关键字对表达式Delegate()求值获取这个对象。任何符合属性代理规则都可以使用by关键字。属性代理类必须要遵循getValue(),setValue()方法约定,getValue、setValue方法可以是普通方法也可以是扩展方法,并且是方法是支持运算符重载。如果是val修饰的属性只需要具备getValue()方法即可。

属性代理基本流程就是代理类中的getValue()方法包含属性getter访问器的逻辑实现,setValue()方法包含了属性setter访问器的逻辑实现。当属性name执行赋值操作时,会触发属性setter访问器,然后在setter访问器内部调用delegate对象的setValue()方法;执行读取属性name操作时,会在getter访问器中调用delegate对象的getValue方法.

  • 3、by关键字

by关键字实际上就是一个属性代理运算符重载的符号,任何一个具备属性代理规则的类,都可以使用by关键字对属性进行代理。

二、常见属性代理基本使用

属性代理是Kotlin独有的特性,我们自己去自定义属性代理,当然Kotlin还提供了几种常见的属性代理实现。例如:Delegates.notNull(), Delegates.observable(), Delegates.vetoable()

  • 1、Delegates.notNull()的基本使用

Delegate.notNull()代理主要用于可以不在构造器初始化时候初始化而是可以延迟到之后再初始化这个var修饰的属性,它和lateinit功能类似,但是也有一些不同,不过它们都需要注意的一点是属性的生命周期,开发者要做到可控,也就是一定要确保属性初始化是在属性使用之前,否则会抛出一个IllegalStateException.

package com.mikyou.kotlin.delegate

import kotlin.properties.Delegates

class Teacher {
   
    var name: String by Delegates.notNull()
}
fun main(args: Array<String>) {
   
    val teacher = Teacher().apply {
    name = "Mikyou" }
    println(teacher.name)
}

可能有的人并没有看到notNull()有什么大的用处,先说下大背景吧就会明白它的用处在哪了?

大背景: 在Kotlin开发中与Java不同的是在定义和声明属性时必须要做好初始化工作,否则编译器会提示报错的,不像Java只要定义就OK了,管你是否初始化呢。我解释下这也是Kotlin优于Java地方之一,没错就是空类型安全,就是Kotlin在写代码时就让你明确一个属性是否初始化,不至于把这样的不明确定义抛到后面运行时。如果在Java你忘记了初始化,那么恭喜你在运行时你就会拿到空指针异常。

问题来了: 大背景说完了那么问题也就来了,相比Java,Kotlin属性定义时多出了额外的属性初始化的工作。但是可能某个属性的值在开始定义的时候你并不知道,而是需要执行到后面的逻辑才能拿到。这时候解决方式大概有这么几种:

方式A: 开始初始化的时给属性赋值个默认值

方式B: 使用Delegates.notNull()属性代理

方式C: 使用lateinit修饰属性

以上三种方式有局限性,方式A就是很暴力直接赋默认值,对于基本类型还可以,但是对于引用类型的属性,赋值一个默认引用类型对象就感觉不太合适了。方式B适用于基本数据类型和引用类型,但是存在属性初始化必须在属性使用之前为前提条件。方式C仅仅适用于引用类型,但是也存在属性初始化必须在属性使用之前为前提条件。

优缺点分析:

属性使用方式 优点 缺点
方式A(初始化赋默认值) 使用简单,不存在属性初始化必须在属性使用之前的问题 仅仅适用于基本数据类型
方式B(Delegates.notNull()属性代理) 适用于基本数据类型和引用类型 1、存在属性初始化必须在属性使用之前的问题;
2、不支持外部注入工具将它直接注入到Java字段中
方式C(lateinit修饰属性) 仅适用于引用类型 1、存在属性初始化必须在属性使用之前的问题;
2、不支持基本数据类型

使用建议: 如果能对属性生命周期做很好把控的话,且不存在注入到外部字段需求,建议使用方式B;此外还有一个不错建议就是方式A+方式C组合,或者方式A+方式B组合。具体看实际场景需求。

  • 2、Delegates.observable()的基本使用

Delegates.observable()主要用于监控属性值发生变更,类似于一个观察者。当属性值被修改后会往外部抛出一个变更的回调。它需要传入两个参数,一个是initValue初始化的值,另一个就是回调lamba, 回调出property, oldValue, newValue三个参数。

package com.mikyou.kotlin.delegate

import kotlin.properties.Delegates

class Person{
   
    var address: String by Delegates.observable(initialValue = "NanJing", onChange = {
   property, oldValue, newValue ->
        println("property: ${
     property.name}  oldValue: $oldValue  newValue: $newValue")
    })
}

fun main(args: Array<String>) {
   
    val person = Person().apply {
    address 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熊喵先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值