kotlin委托(代码片段)

ShouCeng ShouCeng     2022-11-30     396

关键词:

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委托(代码片段)

1、类委托类的委托是一个类中定义的方法实际是调用另一个类的对象的方法来实现的。interfaceIDemofunaction1()funaction2()//被委托的类classDemoImpl:IDemooverridefunaction1()println("doaction1")overridefunaction2()println("doaction2")/ 查看详情

kotlin委托(代码片段)

1、类委托类的委托是一个类中定义的方法实际是调用另一个类的对象的方法来实现的。interfaceIDemofunaction1()funaction2()//被委托的类classDemoImpl:IDemooverridefunaction1()println("doaction1")overridefunaction2()println("doaction2")/ 查看详情

kotlin对象枚举委托(代码片段)

目录一、Kotlin对象1.kotlin伴生对象2.kotlin对象和单例模式二、Kotlin枚举1.kotlin定义枚举2.kotlin使用枚举三、Kotlin委托1.kotlin类委托2.kotlin属性委托3.kotlinMap委托4.kotlin延迟属性5.kotlin属性监听附Github源码一、Kotlin对象1.kotlin伴生对象clas... 查看详情

kotlin学习之委托机制(代码片段)

类的委托    委托模式可以很好的替代实现继承,kotlin本身支持需要零样板代码,一个类Derived可以继承Base并委托它所有的public方法到一个指定的类:interfaceBasefunprint()classBaseImpl(valx:Int):Baseoverridefunprint()print(x)classDerived(b:B... 查看详情

kotlin委托工厂map存储属性值(代码片段)

1.委托工厂(1)概念?委托工厂顾名思义:生产委托对象的工厂类。(2)定义?该类实现了operator修饰的provideDelegate方法,返回ReadWriteProperty/ReadOnlyProperty,该类就可提供对应类型的委托对象。/***委托工厂类(生产委托对象)*/classP... 查看详情

kotlin学习记录(代码片段)

关于kotlin中的委托:1.属性委托  我们知道kotlin为我们提供了几种标准的委托,如下所示  延迟属性(lazyproperties):其值只在?次访问时计算;  可观察属性(observableproperties):监听器会收到有关此属性变更的通知;  把... 查看详情

kotlin实战之委托总结(代码片段)

...是觉得将自己的云笔记分享出来吧~特别说明,kotlin系列文章均以Java差异为核心进行提炼,与Java相同部分不再列出。随着kotlin官方版本的迭代,文中有些语法可能会发生变化,请务必留意,语言领悟精髓... 查看详情

kotlin中委托的概念和原理(代码片段)

kotlin中委托的概念和原理问题背景kotlin的日常使用过程中,经常会使用到委托机制,问题来了,委托机制究竟是什么呢?委托模式:多个对象接收并处理同一请求,他们将请求委托给另一个对象统一处理请求。比如调用A类的metho... 查看详情

kotlin委托必须重视的几个点(代码片段)

委托模式是实现继承的一个很好的替代方式,也是Kotlin语言的一种特性,可以很优雅的实现委托模式。在开发过程中也少不了使用它,但常常都会被低估。所以今天就让它得到重视,去充分的掌握kotlin委托特性以... 查看详情

kotlin委托的本质以及mmkv的应用(代码片段)

作者:DylanCai前言很多人写Kotlin只用到一些Java已有的东西,单纯地把Java代码翻译成Kotlin代码,而Kotlin一些好用的语法糖都没有用过。本文给大家介绍一个Java不常见但是很好用的Kotlin语法糖——Kotlin委托。Kotlin委托包... 查看详情

kotlin小知识之泛型和委托(代码片段)

...属性实现一个自己的lazy函数泛型和委托泛型的基本用法Kotlin当中的泛型机制和Java当中的泛型机制还是有异同的所谓泛型就是说在一般的编程模式下面,我们需要给一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型... 查看详情

databinding——使用kotlin委托优化(代码片段)

简介DataBinding是Google在Jetpack中推出的一款数据绑定的支持库,利用该库可以实现在页面组件中直接绑定应用程序的数据源。使其维护起来更加方便,架构更明确简洁。启用DataBindingDataBinding库与AndroidGradle插件捆绑在一起。... 查看详情

kotlin委托必须重视的几个点(代码片段)

委托模式是实现继承的一个很好的替代方式,也是Kotlin语言的一种特性,可以很优雅的实现委托模式。在开发过程中也少不了使用它,但常常都会被低估。所以今天就让它得到重视,去充分的掌握kotlin委托特性以... 查看详情

kotlin委托属性(代码片段)

funmain(arg:Array<String>)valmyClass1=myClass1()myClass1.name="mycalsss1"valmyClass2=myClass2()myClass2.name="myclass2"println(myClass1.name)println(myClass2.name)classmyClass1varname:String=""get():Stringprintln("mycalsss1.get被调用")returnfieldset(value:String)println("myclass1.set被调... 查看详情

对比java学kotlin代理(代码片段)

...c;而且使用Java无法完成对属性的委托。而我们今天的主角Kotlin则对上述问题做了改进。Kotlin代理在Kotlin中,我们使用关键字by使用委托功能。代理方法Kotlin对方法的委托是借助接口来实现的。具体用法如下:interfaceBasefunpr... 查看详情

优雅地封装mmkv,属性委托原来是这么回事(代码片段)

...不过DataStore的API比SharedPreferences复杂得多,还用到了Kotlin协程的Flow,学习成本比较高。所以个人推荐用腾讯开源的MMKV。MMKV的用法其实已经挺简单了,不过结合Kotlin属性委托会更加好用。应该有很多人对Kotlin的属性委... 查看详情

databinding——使用kotlin委托优化(代码片段)

简介DataBinding是Google在Jetpack中推出的一款数据绑定的支持库,利用该库可以实现在页面组件中直接绑定应用程序的数据源。使其维护起来更加方便,架构更明确简洁。启用DataBindingDataBinding库与AndroidGradle插件捆绑在一起。... 查看详情

对比java学kotlin代理(代码片段)

Java代理当我们需要对某个对外公开的API做一些拦截时,我们可以使用代理。常见的应用场景包括内部校验、将已废弃但是又无法删除的方法委托给新的代理类处理等(有点挂羊头卖狗肉的意思)。比如://内部校... 查看详情