Kotlin基础知识_10-泛型和委托

Kotlin基础知识_10-泛型和委托

1. 泛型

1.1 泛型类与泛型方法

泛型类

class HttpResponse<T> {
    fun parseResponse(inputValue: T): T {
        return inputValue
    }
}

fun main() {
    val httpResponse = HttpResponse<String>()
    val response = httpResponse.parseResponse("success")
    println("response value: $response")
}

使用时,只需要在类名后加上<T>即可。

泛型方法

fun <T> buildHttpRequest(seq: T): String {
    val ret = StringBuilder().run {
        append("seq: $seq\n")
        append("msgLength: 100\n")
        append("msgContent: 123ABC\n")
        toString()
    }
    return ret
}

fun main() {
    val reqMsg = buildHttpRequest(20)
    println("reqMsg: $reqMsg")
}

1.2 对泛型进行限定

ex: 限定只能输入数字

image-20231107144809835

2. 委托

委托机制是一种非常灵活的语言特性,它可以让我们将对象的某些属性或方法委托给其他对象来处理。Kotlin 也支持委托功能,分为类委托属性委托

2.1 类委托

类委托就是把类中的某些方法给委托出去,由委托类去完成。

比如,身为学生就需要写家庭作业:

interface Student {
    fun doHomeWork()
}

雷军同学是个好学生,他马上就把作业完成了:

class LeiJun : Student {
    override fun doHomeWork() {
        println("My name is LeiJun, I have completed the homework")
    }
}

但马云同学是个坏学生,他只想着玩游戏,没办法完成家庭作业,所以他只好委托雷军同学帮他完成家庭作业:

class MaYun(val leiJun: LeiJun) : Student {
    fun playGame() {
        println("My name is MaYun, I like play Games")
    }

    override fun doHomeWork() {
        leiJun.doHomeWork()
    }
}

那这样马云同学就High了, 他既可以玩游戏,也可以完成家庭作业:

interface Student {
    fun doHomeWork()
}

class LeiJun : Student {
    override fun doHomeWork() {
        println("My name is LeiJun, I have completed the homework")
    }
}

class MaYun(val leiJun: LeiJun) : Student {
    fun playGame() {
        println("My name is MaYun, I like play Games")
    }

    override fun doHomeWork() {
        leiJun.doHomeWork()
    }
}

fun main() {
    val leiJun = LeiJun()
    val maYun = MaYun(leiJun)

    leiJun.doHomeWork()
    maYun.playGame()
    maYun.doHomeWork()
}

输出:

My name is LeiJun, I have completed the homework
My name is MaYun, I like play Games
My name is LeiJun, I have completed the homework

可以看到,马云同学在雷军的协助下,也能完成家庭作业(尽管作业实际上不是由他完成的...不过无所谓, 反正也能做家庭作业了)。

这里:

  • MaYun 类将 doHomeWork()的方法实现委托给了 LeiJun去做,从而也能顺利调用 doHomeWork()的实现逻辑;
  • 当调用MaYun类的 doHomeWork()方法时,实际上是调用了 LeiJun类的 doHomeWork()方法;

上面就是一个简单的委托实现, 但是写起来还是比较麻烦(可能你现在还没有感觉到,但是如果 Student接口中定义了很多接口方法时,若是所有接口方法都要由委托类去实现,那就麻烦了),为此,kotlin中引入了一个 by关键字,借助该关键字可以快速实现上述委托模型,上述code中的MaYun类使用kotlin中的by关键字可以改写成:

class MaYun1 : Student by LeiJun() // 方式二
class MaYun2(delegate: Student) : Student by delegate // 方式三

测试:

interface Student {
    fun doHomeWork()
}

class LeiJun : Student {
    override fun doHomeWork() {
        println("My name is LeiJun, I have completed the homework")
    }
}

// 方式一
class MaYun(val leiJun: LeiJun) : Student {
    fun playGame() {
        println("My name is MaYun, I like play Games")
    }

    override fun doHomeWork() {
        leiJun.doHomeWork()
    }
}

class MaYun1 : Student by LeiJun() // 方式二
class MaYun2(delegate: Student) : Student by delegate // 方式三


fun main() {
    MaYun(LeiJun()).doHomeWork()            // 方式一
    MaYun1().doHomeWork()                   // 方式二
    MaYun2(LeiJun()).doHomeWork()           // 方式三
}

输出:

My name is LeiJun, I have completed the homework
My name is LeiJun, I have completed the homework
My name is LeiJun, I have completed the homework

以上三种方式均可以正常输出 doHomeWork()的内容。

当然,如果仍然要保留 playGame(),也可以在类体里面添加:

MaYun1.kt

// 方式二
class MaYun1 : Student by LeiJun() {
    fun playGame() {
        println("My name is MaYun, I like play Games")
    }
}

MaYun2.kt

// 方式三
class MaYun2(val delegate: Student) : Student by delegate {
    fun playGame() {
        println("My name is MaYun, I like play Games")
    }

    fun whoHelpDoHomework() {
        println("The ${delegate.javaClass} help me do homework")
    }
}

可以看到方式三比方式二有一个优势,就是能够获取委托对象的实例,比如我以方式三的形式调用 whoHelpDoHomework()方法,打印出委托对象的实例:

Test.kt

interface Student {
    fun doHomeWork()
}

class LeiJun : Student {
    override fun doHomeWork() {
        println("My name is LeiJun, I have completed the homework")
    }
}

// 方式一
class MaYun(val leiJun: LeiJun) : Student {
    fun playGame() {
        println("My name is MaYun, I like play Games")
    }

    override fun doHomeWork() {
        leiJun.doHomeWork()
    }
}

// 方式二
//class MaYun1 : Student by LeiJun() {
//}

// 方式三
//class MaYun2(delegate: Student) : Student by delegate {
//
//}


fun main() {
    MaYun1().playGame()
    MaYun1().doHomeWork()                   // 方式二

    MaYun2(LeiJun()).doHomeWork()
    MaYun2(LeiJun()).whoHelpDoHomework()
}

运行:

My name is MaYun, I like play Games
My name is LeiJun, I have completed the homework
My name is LeiJun, I have completed the homework
The class com.yongdaimi.kotlin.LeiJun help me do homework

可以看到,能够顺利打印出委托对象的实例:com.yongdaimi.kotlin.LeiJun

这里还有一点需要注意,就是 MaYun1MaYun2不能定义在顶层文件里,否则编译器会报错:

image-20231107174825627

如果想继续在 MaYun1MaYun2中编写方法体,则需要另外新建两个kotlin类:MaYun1MaYun2, 我上面就是新建了两个kotlin类:MaYun1.ktMaYun2.kt

2.2 属性委托

2.2.1 属性委托的含义

类委托可以完成接口方法的委托,除此之外,kotlin还支持属性的委托,语法模板如下:

val/var <属性名>: <类型> by <表达式>

类委托是把接口方法委托出去,那属性委托是委托什么呢?很简单,就是把属性委托出去,对于一个属性,无非有两个操作:获取属性以及修改属性,也就是对应的 get()方法set()方法

属性委托即是将对应的 get()/set() 操作分发给指定委托对象getValue()/setValue() 方法执行;当然,如果是 val 修饰的属性,只需要提供 getValue() 即可,因为这个值不可变,也就不需要setValue()了,

比如下面的例子,我们声明了一个Dog类 和一个Person类,人需要去看狗是否还有吃的,如果没有的话,可以给狗再喂一些吃的:

class Person {
    // 对应属性中的get(), 表示获取数据
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, ${property.name}"
    }

    // 对应属性中的set(), 表示设置数据,只有var修饰的属性会有
    operator fun setValue(thisRef: Any?, property: KProperty<*>, food: String) {
        println("$thisRef, ${property.name}, $food")
    }
}

class Dog {
    var food: String by Person()
}

fun main() {
    val dog = Dog()
    println(dog.food)  // 相当于调用 getValue()
    dog.food = "肉"    // 相当于调用 setValue()
}

运行:

com.yongdaimi.kotlin.Dog@13221655, food
com.yongdaimi.kotlin.Dog@13221655, food, 肉

上面例子中的 var food: String by Person()就是将狗的food属性委托给了Person类,当调用Dog的 getter/setter方法时,会自动调用Person类的 getValue()setValue()方法。

在上面getValue()setValue()的方法中:

  • thisRef: 代表被委托对象: Dog;
  • property: 代表被委托对象的属性的元数据,即food属性的元数据,这些元数据被封装到了 KProperty<*>类中(这里的*号可以看做是java泛型里面的?号),这些元数据包括被委托对象的属性的名称类型可见性等信息。
  • value: 代表 setValue()中要设置的值。

上述委托类Person中的 getValue()setValue()方法需要我们手动编写,有点麻烦,kotlin为我们提供了 ReadOnlyProperty, ReadWriteProperty 接口来简化这一操作:

2.2.2 ReadOnlyProperty 和 ReadWriteProperty

这两个接口位于 package kotlin.properties, Interfaces.kt 下:

ReadOnlyProperty

/**
 * Base interface that can be used for implementing property delegates of read-only properties.
 *
 * This is provided only for convenience; you don't have to extend this interface
 * as long as your property delegate has methods with the same signatures.
 *
 * @param T the type of object which owns the delegated property.
 * @param V the type of the property value.
 */
public fun interface ReadOnlyProperty<in T, out V> {
    /**
     * Returns the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @return the property value.
     */
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
}

ReadWriteProperty

/**
 * Base interface that can be used for implementing property delegates of read-write properties.
 *
 * This is provided only for convenience; you don't have to extend this interface
 * as long as your property delegate has methods with the same signatures.
 *
 * @param T the type of object which owns the delegated property.
 * @param V the type of the property value.
 */
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    /**
     * Returns the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @return the property value.
     */
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V

    /**
     * Sets the value of the property for the given object.
     * @param thisRef the object for which the value is requested.
     * @param property the metadata for the property.
     * @param value the value to set.
     */
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}

使用示例:

class PersonR : ReadOnlyProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "$thisRef, ${property.name}"
    }
}

class PersonRW : ReadWriteProperty<Any, String> {
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return "$thisRef, ${property.name}"
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        println("$thisRef, ${property.name}, $value")
    }
}

class Dog {
    val food: String by PersonR()
    var bread: String by PersonRW()
}

fun main() {
    val dog = Dog()
    println(dog.food)       // 相当于调用 PersonR #getValue()
    println(dog.bread)      // 相当于调用 PersonRW #getValue()
    dog.bread = "黄油面包"   // 相当于调用 PersonRW #setValue()
}

运行:

com.yongdaimi.kotlin.Dog@87aac27, food
com.yongdaimi.kotlin.Dog@87aac27, bread
com.yongdaimi.kotlin.Dog@87aac27, bread, 黄油面包

上面我们Dog类新加一个面包属性 break, 可以看到,可以通过实现 ReadOnlyPropertyReadWriteProperty接口来简化委托属性的实现,通过这种方式与手动实现的方式是一致的,但是比手动实现更快捷,也不易出错。

Note:有一点需要注意,在导入 ReadOnlyPropertyReadWriteProperty接口时,注意使用的是 package kotlin.properties包下的接口,而不是 javafx.beans.property包下的,接口名虽然一样,但是效果是不一样的。

3. Kotlin 标准库提供的几种委托

kotlin 标准库中也提供了几个委托:

  • 延迟委托:其值只有首次访问时计算;
  • 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
  • 把多个属性存储到一个映射(map)中,而不是每个存在单独的字段中。

3.1 延迟委托 (by lazy)

延迟委托会用到kotlin内置的lazy()函数, 这是一个高阶函数,它接收一个Lambda表达式,并返回一个Lazy<T>实例,返回的实例可以作为实现延迟属性的委托对象。

该函数原型如下:

/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
 * the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
 */
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and thread-safety [mode].
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * Note that when the [LazyThreadSafetyMode.SYNCHRONIZED] mode is specified the returned instance uses itself
 * to synchronize on. Do not synchronize from external code on the returned instance as it may cause accidental deadlock.
 * Also this behavior can be changed in the future.
 */
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

使用示例:

class Father {
    val age by lazy {
        println("Get father's age...")
        40
    }
}

fun main() {
    val father = Father()
    println("Father's age: ${father.age}")
    println("Father's age: ${father.age}")
    println("Father's age: ${father.age}")
}

运行:

Get father's age...
Father's age: 40
Father's age: 40
Father's age: 40

可以看到,第一次调用时,会执行Lambda表达式,并记录Lambda表达式返回的结果,后面再读取 age属性时就直接返回之前记录的结果。

另外在调用 lazy() 函数时还可以传入一个 LazyThreadSafetyMode 参数,有三个取值,分别代表不同的意思:

  • LazyThreadSafetyMode.SYNCHRONIZED:该值只在一个线程中计算,并且所有线程会看到相同的值。
  • LazyThreadSafetyMode.PUBLICATION:可以在并发访问未初始化的Lazy实例值时调用多次,但只有第一次返回的值将被用作Lazy实例的值。
  • LazyThreadSafetyMode.NONE:不会有任何线程安全的保证以及相关的开销,当初始化与使用总是位于相同线程中时使用。

如果lazy 属性的求值时不传 LazyThreadSafetyMode 参数,那么默认情况下走的是是LazyThreadSafetyMode.SYNCHRONIZED模式。

ex:

val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    println("Hello,第一次调用才会执行我!")
    "10086"
}

3.2 可观察属性 - observable

如果你要监听属性值的变化,可以将该属性委托给 Delegates.observable, Delegates.observable()可以认为是一个属性监听器,当监听的属性值发生变更时会收到通知。其接收两个参数:初始值initialValueonChange()函数,当属性被赋值后就会触发onChange(),内部有三个参数:被赋值的属性、旧值与新值

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)
        }

使用示例:

class Father {
    var nickName: String by Delegates.observable("unknown name") { proerty, oldName, newName ->
        println("${proerty.name}, changed from $oldName to $newName")
    }
}

fun main() {
    val father = Father()
    father.nickName = "老猫"
    father.nickName = "丁棍"
}

运行:

nickName, changed from unknown name to 老猫
nickName, changed from 老猫 to 丁棍

3.3 可观察属性 - vetoable

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

来看这样一个例子:声明一个Int类型的属性age,如果新的值比旧值大,则生效,否则不生效。

class Father {
    var age: Int by Delegates.vetoable(0) { property, oldAge, newAge ->
        println("Tips: ${property.name} has changed, from $oldAge change to $newAge")
        newAge > oldAge // 如果新的值比旧的值大,则生效,否则则不生效
    }
}

fun main() {
    val father = Father()
    father.age = 10
    father.age = 20
    father.age = 15
    println("my father's age is: ${father.age}")
}

运行:

Tips: age has changed, from 0 change to 10
Tips: age has changed, from 10 change to 20
Tips: age has changed, from 20 change to 15
my father's age is: 20

可以看到,虽然对 age的值做了3次修改,但是father.age = 15这次的修改是不会生效的,因为这次赋的值小于之前赋的旧值。

3.4 属性之间的委托

Kotlin 1.4开始,一个属性可以把它的 gettersetter 委托给另一个属性,被委托的属性可以是顶层属性或普通类的属性均可。

那这个有什么作用呢?

比如我们对外提供了一个属性接口 mVersion ,用于获取当前固件的版本号,后来随着项目迭代我们觉得这样叫太笼统了,可能一个固件里面包含有很多其它子模块如 MCU, DSP 等等,以前的mVersion接口可能当时只是用来表达MCU的version, 为了以后能够更方便的表达各个子模块的版本,我们计划使用 mMcuVersion来替代mVersion的作用,但是SDK已经给到客户了,已经有不少客户使用 mVersion来获取MCU的版本号,为了前后兼容,可以使用kotlin提供的属性委托来解决。

例:

class Firmware {
    @Deprecated("please use the 'mMcuVersion' to get the MCU version")
    var mVersion: String by this::mMcuVersion               // 双冒号前的this可省略
    var mMcuVersion: String = "1.0.0"
}

fun main() {
    val firmware = Firmware()
    firmware.mMcuVersion = "2.0.0"                          // 我们今后只会更新 mMcuVersion的值
    println("current MCU version: ${firmware.mVersion}")    // 旧客户使用
    println("current MCU version: ${firmware.mMcuVersion}") // 新客户使用
}

运行:

current MCU version: 2.0.0
current MCU version: 2.0.0

可以看到,我们将 mVersion属性的 getter/setter 委托给了 mMcuVersion属性,这样今后如果MCU的版本号需要更新,就不用再更新 mVersion属性的值了,直接更新 mMcuVersion的值即可,如果仍然有客户使用 mVersion接口来获取MCU的版本,也没有关系,当读取 mVersion的值时,会将 mMcuVersion的值返回给他。

为将一个属性委托给另一个属性,可以使用 :: 限定符,例如,this::delegateMyClass::delegate

另外,我们还可以使用 @Deprecated 来注解旧的属性、这样当客户调用这个旧的属性时,会有中划线来表示过期,当鼠标移到上面时,会有警告信息。

image-20231108153439341

3.5 属性存储于映射(map)中

还有一种情况,在一个映射(map)里存储属性的值,使用映射实例自身作为委托来实现委托属性,如:

class User(val info: Map<String, Any?>) {
    val name: String by info
    val age: Int by info
}

fun main() {
    val user = User(
        mapOf(
            "name" to "ZhangSan",
            "age" to 20
        )
    )

    println("The user's name: ${user.name}, age: ${user.age}")
}

运行:

The user's name: ZhangSan, age: 20

使用映射实例自身作为委托来实现委托属性,可以使用在json解析中,因为json本身就可以解析成一个map。

<完>

参考链接

  1. Kotlin | 10分钟搞定by委托机制
  2. Kotlin 的类委托与属性委托
  3. 一文彻底搞懂Kotlin中的委托
posted @ 2023-11-08 15:55  夜行过客  阅读(35)  评论(0编辑  收藏  举报