Kotlin:属性委托笔记&实战
委托模式委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。Kotlin直接支持委托模式,更加优雅,简洁。Kotlin通过关键字by实现委托。属性委托属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。属性委托语法格式:val/var <属性名>: &
委托模式
委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
Kotlin直接支持委托模式,更加优雅,简洁。Kotlin通过关键字by实现委托。
属性委托
属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。
属性委托语法格式:
val/var <属性名>: <类型> by <委托代理类>
属性委托要求
-
对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:
- thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
- property —— 必须是类型 KProperty<*> 或其超类型这个函数必须返回与属性相同的类型(或其子类型)。
-
对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:
- property —— 必须是类型 KProperty<*> 或其超类型
- value —— 必须和属性同类型或者是它的超类型。
所以一个最基本的委托类代码如下:
class Delegate {
private var str: String? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
println("${property.name}属值为:$str")
return str ?: "尚未赋值"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("${property.name}属性赋值为:$value")
str = value
}
}
fun main() {
var p: String by Delegate()
println(p) // 访问该属性,调用 getValue() 函数
p = "Runoob" // 调用 setValue() 函数
println(p)
}
输出:
p属值为:null
尚未赋值
p属性赋值为:Runoob
p属值为:Runoob
委托接口
Kotlin基本函数库里专门提供了两个委托接口,方便我们使用:
1. ReadOnlyProperty //只读属性,针对val
2. ReadWriteProperty //读写属性,针对var
标准委托
notNull
notNull适用于那些无法在初始化阶段就确定属性值的场合。
看到这句话我们基本都会想到关键字lateinit,但是我们知道lateinit不可用于基础数据类型。
而notNull可用基础数据类型,基础数据类型一般情况下为空是可直接取默认值,所以我们日常使用lateinit即可代替notNull。
var notNullBar: Int by Delegates.notNull()
notNullBar = 123
println(notNullBar)
延迟属性Lazy
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托:
第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果,
后续调用 get() 只是返回记录的结果。
需要注意的是lazy只适用于val类型。
val lazyValue: String by lazy {
println("computed!") // 第一次调用输出,第二次调用不执行
"Hello"
}
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
输出:
computed!
Hello
Hello
你可以将局部变量声明为委托属性。如下:
memoizedFoo 变量只会在第一次访问时计算。 如果someCondition失败,那么该变量根本不会计算。
fun example(computeFoo: () -> Foo) {
val memoizedFoo by lazy(computeFoo)
if (someCondition && memoizedFoo.isValid()) {
memoizedFoo.doSomething()
}
}
可观察属性值变化Observable
Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
handler有三个参数:被赋值的属性、旧值和新值。
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("旧值:$old -> 新值:$new")
}
name = "第一次赋值"
name = "第二次赋值"
输出:
旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值
映射属性的值
经常出现在像解析 JSON 或者做其他"动态"事情的应用中。
在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
class Site(map: MutableMap<String, Any?>) {
val name: String by map
val url: String by map
}
fun main() {
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
Kotlin委托简化获取Intent参数的方式
前面简单介绍了委托及使用。接下来我们动手实战,使用委托来简化获取Intent参数的方式。
一般我们获取Intent参数,如果要全局使用时,需要先声明一个var变量,然后再onCreate中通过getXXXExtra()方法获取。大致代如下:
private var test: String? = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
test = intent.getStringExtra("test")
}
如果我们直接这么写会,运行时会报错val test: String = intent.getStringExtra("test"),因为属性初始化的时候getIntent返回的是空,只有在onCreate之后调用才能保证不为空。
那我们换个写法:
private val test by lazy {
intent.getStringExtra("test")
}
嗯,这样写就省事很多了,我们使用lazy延时加载的特性,避免了intent为空的情况。但是前面我们说过lazy只适用于val类型声明,属性不能修改,显然不是很符合我们日常开发的需求。
我们知道ReadWriteProperty接口适用于读写var类型的声明,因此我们可以实现ReadWriteProperty接口,来完成我们的需求。
class ActivityExtras<T>(private val extraName: String, private val defaultValue: T) : ReadWriteProperty<Activity, T> {
/**
* getExtras字段对应的值
*/
private var extra: T? = null
override fun getValue(thisRef: Activity, property: KProperty<*>): T {
// 如果extra不为空则返回extra
// 如果extra是空的,则判断intent的参数的值,如果值不为空,则将值赋予extra,并且返回
// 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra
return extra ?: thisRef.intent?.get<T>(extraName)?.also { extra = it }
?: defaultValue.also { extra = it }
}
override fun setValue(thisRef: Activity, property: KProperty<*>, value: T) {
extra = value
}
}
thisRef.intent?.get<T>(extraName)这段代码的get扩展函数见https://blog.csdn.net/u011387817/article/details/99844066,该文使用反射简化了获取intent参数是的类型,非常方便。
也可以访问源码参考。
当然你也可以根据泛型T的类型,使用is判断它的类型,然后自己根据种类逐个区分开来。这里不是重点,就不赘述了。
有了上面的实现,我们进一步使用扩展函数简化默认值:
fun <T> extraAct(extraName: String): ActivityExtras<T?> = ActivityExtras(extraName, null)
fun <T> extraAct(extraName: String, defaultValue: T): ActivityExtras<T> = ActivityExtras(extraName, defaultValue)
接下来在Activity中使用:
var list: ArrayList<String> by extraAct("params", arrayListOf())
是不是非常的清爽? Fragment中实现也基本一致:
/**
* 获取Intent参数,Fragment
* 示例同[ActivityExtras]
*/
class FragmentExtras<T>(private val extraName: String, private val defaultValue: T) : ReadWriteProperty<Fragment, T> {
/**
* getExtras字段对应的值
*/
private var extra: T? = null
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
// 如果extra不为空则返回extra
// 如果extra是空的,则判断intent的参数的值,如果值不为空,则将值赋予extra,并且返回
// 如果intent参数的值也为空,则返回defaultValue,并且将值赋予extra
return extra ?: thisRef.arguments?.get<T>(extraName)?.also { extra = it }
?: defaultValue.also { extra = it }
}
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
extra = value
}
}
fun <T> extraFrag(extraName: String): FragmentExtras<T?> = FragmentExtras(extraName, null)
fun <T> extraFrag(extraName: String, defaultValue: T): FragmentExtras<T> = FragmentExtras(extraName, defaultValue)
以上源码请见:Github源码
参考
https://www.runoob.com/kotlin/kotlin-delegated.html
https://blog.csdn.net/u011387817/article/details/99844066
https://www.jowanxu.top/2019/08/27/Kotlin_intent_get_extra/
更多推荐



所有评论(0)