Kotlin委托

Posted ShouCeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin委托相关的知识,希望对你有一定的参考价值。

1、类委托

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

interface IDemo 
    fun action1()
    fun action2()


//被委托的类
class DemoImpl : IDemo 
    override fun action1() 
        println("do action1 ")
    

    override fun action2() 
        println("do action2")
    


//通过by建立委托类
class DemoImplDelegate1 (private val demo:IDemo):IDemo by demo
class DemoImplDelegate1 :IDemo by DemoImpl()

fun main(args: Array<String>) 
    val demoImpl = DemoImpl()
    DemoImplDelegate1(demoImpl).action1()


class DemoImplDelegate2(private val demo:IDemo):IDemo by demo 
    override fun action1() 
        println("delegate before")
        demo.action()
        println("delegate after")
    
    
    fun action() 
    

2、属性委托

class Test 
    // 属性委托
    var prop: String by Delegate()

委托属性的语法如下:
⁣val/var <属性名>: <类型> by <表达式>
和类委托原理一样,被代理的逻辑就是这个属性的get/set方法。get/set会委托给被委托对象的setValue/getValue方法,因此被委托类需要提供setValue/getValue这两个方法。如果是val 属性,只需提供getValue。如果是var 属性,则setValue/getValue都需要提供。

class Delegate 
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String 
        return "$thisRef, thank you for delegating '$property.name' to me!"
    

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) 
        println("$value has been assigned to '$property.name' in $thisRef.")
    

  • thisRef —— 必须与属性所有者 类型相同或者是它的超类型;
  • property —— 必须是类型 KProperty<*>或其超类型。
  • value —— 必须与属性同类型或者是它的子类型。
    通过上面的原理分析了解,要实现属性委托,就必须要提供getValue/setValue方法,但实现起来比较复杂,比如复杂的参数,还要每次都要手写,容易写错。
    Kotlin 标准库中声明了2个含所需 operator方法的 ReadOnlyProperty / ReadWriteProperty 接口
interface ReadOnlyProperty<in R, out T> 
    operator fun getValue(thisRef: R, property: KProperty<*>): T


interface ReadWriteProperty<in R, T> 
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)

被委托类实现这两个接口其中之一就可以了,val 属性实现ReadOnlyProperty,var属性实现ReadWriteProperty。

// val 属性委托实现
class Delegate1: ReadOnlyProperty<Any,String>
    override fun getValue(thisRef: Any, property: KProperty<*>): String 
        return "通过实现ReadOnlyProperty实现,name:$property.name"
    

// var 属性委托实现
class Delegate2: ReadWriteProperty<Any,Int>
    override fun getValue(thisRef: Any, property: KProperty<*>): Int 
        return  20
    

    override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) 
       println("委托属性为: $property.name 委托值为: $value")
    


// 测试
class Test 
    // 属性委托
    val d1: String by Delegate1()
    var d2: Int by Delegate2()


fun main(args: Array<String>) 
    val test = Test()
    println(test.d1)
    println(test.d2)
    test.d2 = 100

执行结果:
通过实现ReadOnlyProperty实现,name:d1
20
委托属性为: d2 委托值为: 100

3、Kotlin标准库中的委托

1)延迟属性Lazy

val lazyValue: String by lazy 
    println("只执行第一次!")     // 第一次调用输出,第二次调用不执行
    "你好"


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

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

  • LazyThreadSafetyMode.SYNCHRONIZED:添加同步锁,使lazy延迟初始化线程安全
  • LazyThreadSafetyMode.PUBLICATION:初始化的lambda表达式可以在同一时间被多次调用,但是只有第一个返回值作为初始化的值。
  • LazyThreadSafetyMode.NONE:没有同步锁,多线程访问时初始化的值是未知的,非现场安全,一般情况下不推荐使用,除非能保证初始化和属性初始化始终在同一个线程。
val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) 
    println("只执行第一次!")
    "你好"

默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在与属性使用位于相同的线程, 那么可以使用 LazyThreadSafetyMode.NONE 模式:它不会有任何线程安全的保证以及相关的开销。

2)几种常见的属性代理实现

Delegates是一个代理单例对象,里面有notNull、observable、vetoable静态方法,每个方法返回不同的类型代理对象。

2.1)Delegates.observable

如果想要观察一个属性的变化过程,可以将属性委托给Delegates.observable.

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 = "第二次赋值" //旧值:第一次赋值 -> 新值:第二次赋值

Delegates.observable()函数接受两个参数:第一个是初始化值,第二个是属性值变化时的回调处理器。回调有三个参数:被赋值的属性property、旧值oldValue、新值newValue。

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)
 

ReadWriteProperty位于package kotlin.properties,用力一瞥源码

public abstract class ObservableProperty<T>(initialValue: T) : ReadWriteProperty<Any?, T> 
    private var value = initialValue
    ...
    protected open fun afterChange(property: KProperty<*>, oldValue: T, newValue: T): Unit 

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T 
        return value
    

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) 
        ...
        this.value = value
        afterChange(property, oldValue, value)
    

2.2)Delegates.vetoable函数

vetoable与observable一样可以观察属性值的变化,不同的是vetoable可以通过处理器函数来决定属性值是否生效。

var vetoableProp: Int by Delegates.vetoable(0)
        _, oldValue, newValue ->
    // 如果新的值大于旧值,则生效
    newValue > oldValue

fun main(args: Array<String>) 
    val user = User()
    println("vetoableProp=$vetoableProp")  //0
    vetoableProp = 10
    println("vetoableProp=$vetoableProp")  //10
    vetoableProp = 5
    println("vetoableProp=$vetoableProp")  //10
    vetoableProp = 100
    println("vetoableProp=$vetoableProp")  //100

如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。

2.3)Delegates.notNull

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

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

notNull 适用于那些无法在初始化阶段就确定属性值的场合,如果属性在赋值前就被访问的话则会抛出异常。
notNull()首先是一个方法,返回的是一个NotNullVar属性代理实例:

private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> 
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T 
        return value ?: throw IllegalStateException("Property $property.name should be initialized before get.")
    

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) 
        this.value = value
    

实际上可以理解在访问器getter加了一层判空的代理实现。
相比Java,Kotlin属性定义时需要额外的属性初始化的工作。但是可能某个属性的值在开始定义时并不知道,需要执行到后面的逻辑才能拿到。可以实现的方法:

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

3)属性储存在映射中

在一个映射(map)里存储属性的值,使用映射实例自身作为委托来是爱心委托属性:

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 "koltin委托",
        "url"  to "https://xiaomi.f.mioffice.cn/ "
    ))
    
    // 读取映射值
    println(site.name) //koltin委托
    println(site.url)  //https://xiaomi.f.mioffice.cn/

也可以替换成可变的Map:

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 "koltin委托",
            "url" to "https://xiaomi.f.mioffice.cn/"
    )
    val site = Site(map)

    println(site.name)
    println(site.url) //同上

4)局部委托属性

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

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

可以将局部变量声明为委托属性,memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。

5)提供委托

通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来创建属性委托实例。接口PropertyDelegateProvider(不用实现这个接口,提供provideDelegate方法即可)。

class People 
    val name: String by DelegateProvider(0)
    val address: String by DelegateProvider(1)

 
class DelegateProvider(delegateType:Int) 
     private val mDelegateType = delegateType
    operator fun provideDelegate(thisRef: People, property: KProperty<*>): ReadOnlyProperty<People, String> 
        println("I'm in provideDelegate.")
        checkProperty()
        if(mDelegateType == 0 
            return RealDelegate1()
         else 
            return RealDelegate2()
        
    
 
    private fun checkProperty() 
        val random = Random.Default
        if (!random.nextBoolean()) 
            throw RuntimeException("failed to create delegate.")
        
    


class RealDelegate : ReadOnlyProperty<People, String> 
     override fun getValue(thisRef: People, property: KProperty<*>): String 
         return "kotlin"
     


class RealDelegate2: ReadOnlyProperty<People, String> 
     override fun getValue(thisRef: People, property: KProperty<*>): String 
         return "Java"
     

必须提供一个provideDelegate的方法,参数和getValue相同,方法签名:

operator fun provideDelegate(thisRef: T, property: KProperty<*>): RealOnlyProperty<T, R>
或者
operator fun provideDelegate(thisRef: T, property: KProperty<*>): ReadWriteProperty<T, R>

运行结果

fun main() 
    val people = People()
    println(people.name)
    println(people.address)

4、总结

委托在Kotlin中占有很重要的应用,特别是属性委托比如lazy延迟初始化使用。委托模式是一项技巧,其他的几种设计模式如:策略模式、状态模式和访问者模式都是委托模式的具体场景应用。
by关键字是一种约定,是对委托类的方法的约定,就是简化函数调用。这里的by约定简化了属性的get和set方法的调用,当然委托类需要定义相应的函数:getValue,setValue。

以上是关于Kotlin委托的主要内容,如果未能解决你的问题,请参考以下文章

kotlin—lazy及其原理

如何为 kotlin 委托属性使用不同的类型

Kotlin的延迟初始化

Kotlin 对象枚举委托

如何用值初始化 Kotlin 中的数组?

委托-kotlin