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

工匠若水 工匠若水     2022-12-07     615

关键词:

工匠若水可能会迟到,但是从来不会缺席,最终还是觉得将自己的云笔记分享出来吧 ~

特别说明,kotlin 系列文章均以 Java 差异为核心进行提炼,与 Java 相同部分不再列出。随着 kotlin 官方版本的迭代,文中有些语法可能会发生变化,请务必留意,语言领悟精髓即可。

kotlin 委托

委托模式是软件设计模式中的一个常用技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。在 java 中实现委托需要我们自己设计代码结构去完成,而 kotlin 直接语法层面支持委托模式,其实现更加优雅、简洁,kotlin 通过关键字 by 来实现委托。

kotlin 类委托

类委托的原理是 by 关键字后面的对象实际会被存储在类的内部,编译器则会将父接口的所有方法实现出来,并且将其转移给委托对象去执行。如下就是一个典型案例:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

//定义一个父接口
interface InterfaceBase 
    fun print(i: Int)


//定义接口的实现类
class InterfaceBaseImpl (var index: Int): InterfaceBase 
    override fun print(i: Int) 
        println("impl index is $index, params is $i")
    


//类似java方式的kotlin委托实现
class InterfaceBaseDelete(private val base: InterfaceBase): InterfaceBase 
    override fun print(i: Int) 
        base.print(i)
    


//kotlin推荐的类委托实现
//通过 by 关键字就能省略类体达到上面类似java方式的实现
class InterfaceKotlinDelete(private val base: InterfaceBase): InterfaceBase by base

//kotlin推荐的类委托实现的重写
class InterfaceKotlinDelete1(private val base: InterfaceBase): InterfaceBase by base 
    override fun print(i: Int) 
        //优先使用自己的实现
        println("override--params is $i, delete class is $base.javaClass")
        //自己实现又调用了委托对象的实现
        base.print(i)
    


/**
 调用结果
 impl index is 2, params is 1
 impl index is 2, params is 4
 impl index is 2, params is 5
 override--params is 6, delete class is class cn.yan.test.InterfaceBaseImpl
 impl index is 2, params is 6
 */
fun testRun() 
    val base = InterfaceBaseImpl(2)
    base.print(1)

    val base1 = InterfaceBaseDelete(base)
    base1.print(4)

    val base2 = InterfaceKotlinDelete(base)
    base2.print(5)

    val base3 = InterfaceKotlinDelete1(base)
    base3.print(6)

为了弄清 kotlin 委托类的实现原理,下面看下反编译结果:

yandeMacBook-Pro:test yan$ javap -c InterfaceKotlinDelete.class
Compiled from "Test2.kt"
//InterfaceKotlinDelete实现了InterfaceBase接口
public final class cn.yan.test.InterfaceKotlinDelete implements cn.yan.test.InterfaceBase 
  //构造方法接收一个 InterfaceBase 接口类型参数
  public cn.yan.test.InterfaceKotlinDelete(cn.yan.test.InterfaceBase);
    Code:
       0: aload_1
       1: ldc           #12                 // String base
       3: invokestatic  #18                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: invokespecial #21                 // Method java/lang/Object."<init>":()V
      10: aload_0
      11: aload_1
      12: putfield      #23                 // Field base:Lcn/yan/test/InterfaceBase;
      15: return
  //编译器帮忙实现了委托对象的方法,并且调用了构造函数参数传递对象的对应方法
  public void print(int);
    Code:
       0: aload_0
       1: getfield      #23                 // Field base:Lcn/yan/test/InterfaceBase;
       4: iload_1
       5: invokeinterface #29,  2           // InterfaceMethod cn/yan/test/InterfaceBase.print:(I)V
      10: return

kotlin 属性委托

对于 kotlin 的属性委托来说,我们有如下要求:

  • 对于只读属性来说(val 修饰的属性),委托需要提供一个名为 getValue 的方法,该方法需要提供的参数如下:

    • thisRef:需要是属性拥有者相同的类型或者是其父类型(对于扩展属性来说,这个类型指的是被扩展的那个类型)。
    • property:需要是KProperty<*>类型或者是其父类型。
      getValue 方法需要返回与属性相同的类型或者其子类型。
  • 对于可变读写属性(var 修饰的属性),委托需要提供只读属性的 getValue 方法外,还需要提供一个名为 setValue 的方法,该方法需要提供的参数如下:

    • thisRef:需要是属性拥有者相同的类型或者是其父类型(对于扩展属性来说,这个类型指的是被扩展的那个类型)。
    • property:需要是KProperty<*>类型或者是其父类型。
    • value:需要与属性的类型相同或是其父类型。
  • getValue、setValue 方法既可以作为委托类的成员方法实现,也可以作为其扩展方法来实现。

  • getValue、setValue 方法都必须要标记为 operator 关键字。对于委托类来说,它可以实现 ReadOnlyProperty 或是 ReadWriteProperty 接口,这些接口包含了相应的 getValue、setValue 方法。对于委托类来说,它也可以不实现上面两个接口,而单独提供符合约定的 getValue、setValue 方法,其效果是一样的。

我们看一个属性委托样例:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

//属性委托方法定义有严格的格式要求
//两个方法的定义签名必须按照要求来,不能修改
class PropertyDelete 
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String 
        return "$thisRef, your deleted property name is $property.name"
    

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) 
        println("$thisRef, new value is $value")
    


class PropertyClass 
    //通过属性委托,不用给 name 赋值,因为其 set 和 get 方法都被委托到了 PropertyDelete 对象
    var name: String by PropertyDelete()


/**
 调用结果
 cn.yan.test.PropertyClass@30946e09, new value is 7890
 cn.yan.test.PropertyClass@30946e09, your deleted property name is name
 */
fun testRun() 
    val test = PropertyClass()
    test.name = "7890"
    println(test.name)

属性委托有四种情况在实际开发中比较常用:

  • 延迟属性。
  • 非空属性。
  • 可观测属性。
  • map 委托。

延迟属性: 属性只有在第一次访问时才会计算,之后则会将之前的计算结果缓存起来供后续调用。如下是一个案例:

//延迟属性:依赖 kotlin 提供的 lazy 函数实现,函数参数是一个 lambada 表达式
//源码 LazyJVM.kt 中 public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) 方法
val lazyValue: Int by lazy 
    println("lazyValue lazy")
    28


/**
 调用结果
 lazyValue lazy
 28
 28
 */
fun testRun() 
    //首次调用时触发计算
    println(lazyValue)
    //可以看到后续调用是直接用了上次的缓存结果
    println(lazyValue)

非空属性: 适用于那些无法在初始化阶段确认属性值的场合。lateinit 修饰符只能在类(不在主构造函数中)内声明的var 属性上使用,而且只有在该属性没有自定义集合或者设置器时,此外属性的类型必须是非空的,并且不能是基元类型。而非空属性没有这些限制。其他他们的作用是相同的。如下是一个案例:

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

class Tree 
    //非空属性解决了 var name: String? = null 导致后续判断冗余
    //非空属性解决了 var name: String = "" 初值隐晦问题
    //非空属性解决了 lateinit 的一些缺陷,譬如 lateinit 只能应用于非基元类型,譬如不能用于 Int 等问题
    var name: String by Delegates.notNull<String>()


/**
 调用
 */
fun testRun() 
    val tree = Tree()
    //运行时异常,没有赋值而使用 IllegalStateException: Property name should be initialized before get.
    //println(tree.name)
    tree.name = "123"
    println(tree.name)

可观测属性: kotlin 提供了 observable 赋值后观测器和 vetoable 赋值前拦截观测器的能力。如下是一个案例:

class Tree 
    //可观测属性初值,10 是属性初值,当属性被赋值后会触发回调 lambada 表达式
    var age: Int by Delegates.observable(10) 
        property, oldValue, newValue ->
        println("property name is $property.name, old value is $oldValue, new value is $newValue")
    

    //可观测属性初值,10 是属性初值,当属性被赋值前会触发回调 lambada 表达式,可以做到类似属性值赋值拦截器的效果
    //这里当赋值小于等于0则丢弃
    var defaultCount: Int by Delegates.vetoable(10) 
        property, oldValue, newValue -> when 
            newValue <= 0 -> 
                println("unsupport value of $newValue, refused!")
                return@vetoable false
            
            else -> 
                println("property name is $property.name, old value is $oldValue, new value is $newValue")
                return@vetoable true
            
        
    


/**
 调用结果
 property name is age, old value is 10, new value is 10
 property name is age, old value is 10, new value is 11
 property name is age, old value is 11, new value is 12
 12
 -------------
 property name is defaultCount, old value is 10, new value is 100
 unsupport value of -2, refused!
 property name is defaultCount, old value is 100, new value is 101
 101
 */
fun testRun() 
    val tree = Tree()
    tree.age = 10
    tree.age = 11
    tree.age = 12
    println(tree.age)
    println("-------------")
    tree.defaultCount = 100
    tree.defaultCount = -2
    tree.defaultCount = 101
    println(tree.defaultCount)

map 委托: 可以将属性值存储到 map 当中。通常出现在 json 解析或者一些动态行为,在这种情况中可以将 map 实例作为类中属性的委托。注意:map 中的 key 名字必须要和属性的名字一致才行,否则委托后运行解析时会抛出 NoSuchElementException 异常提示 key name 不存在。下面是只读 map 属性委托的案例:

class Result (map: Map<String, Any?>) 
    val name: String by map
    val address: String by map
    val age: Int by map
    val date: Date by map

    override fun toString(): String 
        return "name: $this.name, address: $this.address, " +
                "age: $this.age, date: $this.date"
    


/**
 调用结果
 name: ruoshui, address: zhuhai, age: 18, date: Sun Oct 18 14:50:21 CST 2019
 */
fun testRun() 
    val result = Result(mapOf(
        "name" to "ruoshui",
        "address" to "zhuhai",
        "age" to 18,
        "date" to Date()
    ))
    println(result.toString())

下面是读写 map 属性委托的案例(可以看到背后都是交给 map 存储的,写属性后 map 会跟着变化):

//【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题  未经允许严禁转载 https://blog.csdn.net/yanbober】

class Result (map: MutableMap<String, Any?>) 
    var name: String by map
    var address: String by map
    var age: Int by map
    var date: Date by map

    override fun toString(): String 
        return "name: $this.name, address: $this.address, " +
                "age: $this.age, date: $this.date"
    


/**
 调用结果
 name: ruoshui, address: zhuhai, age: 18, date: Sun Oct 18 14:57:25 CST 2020
 ruoshui
 --------
 name: gongjiang, address: zhuhai, age: 18, date: Sun Oct 18 14:57:25 CST 2020
 gongjiang
  */
fun testRun() 
    val map: MutableMap<String, Any?> = mutableMapOf(
        "name" to "ruoshui",
        "address" to "zhuhai",
        "age" to 18,
        "date" to Date()
    )
    val result = Result(map)
    println(result.toString())
    println(map["name"])
    println("--------")
    result.name = "gongjiang"
    println(result.toString())
    println(map["name"])

对于每个委托属性来说,kotlin 编译器在底层会生成一个辅助的属性,然后将原有属性的访问委托给这个辅助属性。比如说,对于属性 prop 来说,kotlin 编译器所生成的隐含属性为prop$delete,然后对原有的 prop 属性的访问器的访问都只是委托给了这个额外的辅助属性。

提供委托(providing a delegate)

通过定义 provideDelegate operator,我们可以扩展委托的创建过程逻辑。如果对象定义了 provideDelegate 方法,那么该方法就会被调用来创建属性的委托实例。下面是一个案例:

//委托实现类
class PropertyDelete: ReadOnlyProperty<AnimBase, String> 
    override fun getValue(thisRef: AnimBase, property: KProperty<*>): String 
        return "$thisRef, your deleted property name is $property.name"
    

//提供委托实现:作用是当满足特定条件才给属性添加委托
class AnimBaseLauncher 
    operator fun provideDelegate(thisRef: AnimBase, property: KProperty<*>): ReadOnlyProperty<AnimBase, String> 
        println("AnimBaseLauncher provideDelegate invoke...")
        //这里可以类似工厂依据不同条件生产不同的委托属性或者处理逻辑
        when (property.name) 
            "name", "address" -> return PropertyDelete()
            else -> throw Exception("property name not valid!")
        
    


//普通类
class AnimBase 
    val name: String by AnimBaseLauncher()
    val address: String by AnimBaseLauncher()
    //打印输出第二个 AnimBaseLauncher provideDelegate invoke... 后报错 property name not valid!
    //val work: String by AnimBaseLauncher()


/**
 调用结果:
 AnimBaseLauncher provideDelegate invoke...
 AnimBaseLauncher provideDelegate invoke...
 cn.yan.test.AnimBase@6e5e91e4, your deleted property name is name
 cn.yan.test.AnimBase@6e5e91e4, your deleted property name is address
  */
fun testRun() 
    val anim = AnimBase()
    println(anim.name)
    println(anim.address)

这玩意用的不多,我们知道有这个东西就行,一般自己要是写框架啥的可能会有机会用到。

【工匠若水 加微信 yanbo373131686 联系我,关注微信公众号:码农每日一题 未经允许严禁转载 https://blog.csdn.net/yanbober

kotlin实战之面向对象特性全方位总结(代码片段)

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

kotlin实战之函数与lambda表达式总结(代码片段)

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

kotlin实战之泛型与逆变协变总结(代码片段)

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

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

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

kotlin易踩坑之委托的使用(代码片段)

文章目录前言一、Koltin委托避雷指南总结前言刚开始接触kotlin的委托,深深的koltin的委托吸引,初见,soeasy,真的是码农的福音,语法糖,简洁;委托,真是方便。一、Koltin委托避雷指南我们平时... 查看详情

kotlin易踩坑之委托的使用(代码片段)

文章目录前言一、Koltin委托避雷指南总结前言刚开始接触kotlin的委托,深深的koltin的委托吸引,初见,soeasy,真的是码农的福音,语法糖,简洁;委托,真是方便。一、Koltin委托避雷指南我们平时... 查看详情

算法系列之--kotlin的算法实战比较(原)(代码片段)

前面几节我们介绍了各种算法的具体实现,这一节我们分别对以上算法进行性能测试。    测试方法如下,分别测试n=1000,10000,100000(十万),500000(五十万),1000000(一百万),3000000(三百万)情况下的运算效率,随机数... 查看详情

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

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

kotlin元编程之ksp实战:通过自定义注解配置compose导航路由(代码片段)

在上一篇Kotlin元编程之KSP全面突破中,通过几个设计模式相关的例子来生成代码,其逻辑都比较简单,没有涉及到Android相关的API业务类,而本文的例子会涉及到使用AndroidAPI相关的代码。在之前JetpackCompose中的导航... 查看详情

深入kotlin-委托(代码片段)

委托委托不是java中的概念。它仅仅是一种设计模式,但是kotlin从语言层级上,通过by关键字提供了对委托模式的支持。类委托interfaceMyInteface//1 funmyPrint()classMyInterfaceImpl(valname:String):MyInterface//2 overridefunmyPring() println(name) 查看详情

kotlin委托(代码片段)

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

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