Kotlin小知识之泛型和委托

Posted z啵唧啵唧

tags:

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

文章目录

泛型和委托

泛型的基本用法

  • Kotlin当中的泛型机制和Java当中的泛型机制还是有异同的
  • 所谓泛型就是说在一般的编程模式下面,我们需要给一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好的扩展性.
  • 比如List是一个可以存放数据的列表,但是List并灭有限制我们只能存放整形数据或者字符串数据,因为他没有指定一个具体的类型,而是使用泛型来实现,就是因为如此我们才可以使用List, List…
  • 泛型主要有两种定义方式,一种定义泛型类,一种是定义泛型方法,使用的语法结构都是,当然括号当中的T并不是固定要求的,事实上你写任何英文字母都可以,但是通常情况下,T是一种约定俗成的泛型写法.
  • 如果我们要定义一个泛型类,就可以这样写
class MyClass<T> 
    fun method(param: T) : T 
        return param
    

  • 此时MyClass类就是一个泛型类,MyClass中的方法允许使用T类型的参数和返回值
  • 我们在调用MyClass类和method()方法的时候,就可以将泛型指定成具体的类型,如下所示
val myClass = MyClass<Int>()
val res = myClass.method(123)
  • 这里我们将MyClass类的泛型定义成为了Int类型,于是method()就可以接收一个Int类型的参数了,并且返回值也变成了Int类型
  • 但是如若我们不想定义一个泛型类,只想定义一个泛型方法,可以这样做
  • 只需要将定义泛型的语句结构写在方法上面就可以了,如下所示
class MyClass 
    fun <T> method(param: T) : T 
        return param
    

  • 此时的调用方式也需要做如下的调整
val myClass = MyClass()
val res = myClass.method<Int>(123)
  • 可以看到,现在是在调用method()方法的时候指定泛型数据
  • 另外Kotlin还拥有非常出色的类型推到机制,例如我们传入了一个Int类型的参数,它能够自动推导出泛型的类型是Int类型,于是我们在调用的时候还可以使用这种简便写法
val myClass = MyClass()
//传入Int类型的参数,自动能推导出泛型的类型为Int
val res = myClass.method(123)
  • Kotlin还允许我们对泛型的类型进行限制.比如我们不想将method()方法的泛型指定成任意类型,我们可以通过指定上界的方式来对泛型进行约束,比如在这个地方将method()方法的泛型指定成为Number类型,如下所示
class MyClass 
    fun <T: Number> method(param: T) : T 
        return param
    

  • 这种方法就表明,我们只能将nethod()方法的泛型指定成为数字类型,比如:Int, Float,Double等
  • 另外在默认的情况下,所有的泛型都是可以指定为可空类型的,这是因为在不手动指定上界的情况下,泛型上街默认是Any?
  • 而如果想要泛型的类型不可为空,只需要将泛型的上界手动指定成Any就可以了
  • 之前给StringBuilder类编写过一个build函数,代码如下
fun StringBuilder.build(block: StringBuilder.() -> Unit) : StringBuilder 
    block()
    return this

  • 这个函数的作用和apply函数基本是一样的,但是build函数只能在StringBuilder类中生效,而apply函数是可以作用在所有类上面的,使用泛型的知识可以对build函数进行扩展,让他实现和apply函数完全一样的功能
  • 其实并不复杂,只需要使用将build函数定义成为泛型函数,再将原来所有强制指定StringBuilder的地方替换成为T就可以了
fun <T>.build(block: T.() -> Unit) : T 
    block()
    return this

  • 这样编写出来之后,就可以像apply函数一样去使用build函数了

类委托和委托属性

  • 委托是一种设计模式,它的基本思想是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去进行处理
  • 在Java当中没有对于委托实现语言层面的实现,而在C#等语言当中就对委托进行了原生的支持
  • 在Kotlin当中也是支持委托的,并且将委托功能分为了两种:类委托和委托属性

类委托

  • 类委托的核心思想是:将一个类的具体实现委托给另外一个类去完成
  • 我们曾经使用过Set这种数据类型,他和List比较类似,只是它所存储的数据都是无序的,并且不能重复存放数据
  • Set是一个接口,如果要使用它的话,需要他的具体实现,比如HashSet,而借助于委托模式,我们可以轻松实现一个自己的实现类
  • 定义一个MySet,并让他实现Set接口,代码如下所示
class MySet<T>(val helperSet: HashSet<T>) : Set<T> 
    override val size: Int
        get() = helperSet.size
    override fun contains(element: T) = helperSet.contains(element)
    override fun contanis(elements: Collection<T>) = helperSet.containsAll(elements)
    override fun isEmpty() = helperSet.isEmpty()
    override fun iterator() = helperSet.iterator()

  • 可以看到,MySet的构造函数中接受了一个HashSet参数,这就相当于一个辅助对象,然后在Set接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象的方法进行实现,这个就是一种委托模式.
  • 那么这种写法的好处是什么呢?加入我们让大部分的方法实现调用辅助对象当中的方法,少部分的方法实现由自己重写,甚至加入一些自己独有的方法,那么MySet就会成为一个全新的数据结构类,这就是委托模式的意义所在.
  • 但是这种写法也有自己的弊端,如果接口中待实现的方法比较少比较好,要是有几十甚至几百个方法的话,每个都去这样调用辅助对象中相应方法实现,呢就要写哭了.
  • 在Kotlin当中针对这个问题采用了类委托的方式进行解决.
  • Kotlin当中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模板式的代码了,如下所示:
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helpSet 
    

  • 这段代码的作用和上述代码的实现效果是一模一样的,但是借助了类委托之后,代码明显简化了很多
  • 另外,如果我们想要对某个方法重新进行实现,只需要单独重写那一个方法就行了,其他的方法仍然可以享受类委托所带来的便利,如下所示
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet 
    fun helloWorld() = println("Hello World")
    override fun isEmpty() = false

  • 在这里我们新增了一个helloWorld()方法,并且重写了isEmpty()方法,让他永远返回false,现在我们的MySet就成为了一个全新的数据结构类

委托属性

  • 委托属性的核心思想是将一个属性(字段)的具体实现委托给另外一个类去完成
  • 委托属性的语法结构
class MyClass 
    var p by Delegate()

  • 可以看到这里使用了by关键字连接左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给Delegate类去实现.
  • 当调用p属性的时候,会自动调用Delegate类的getValue()方法,当给p属性赋值的时候,会自动调用Delegate类的setValue()方法
  • 因此我们还需要对Delegte类进行具体实现才行
class Delegate 
    var propValue: Any? = null
    operator fun getValue(myClass: MyClass, prop: KProperty<*>) : Any? 
        return propValue
    
    operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) 
        propValue = value
        

  • 这是一种标准的代码实现模板,再Delegate类中我们必须实现getValue()和setValue()方法,并且都要使用operator关键字进行声明
  • getValue()方法要接收两个参数,第一个参数用于声明该Delegate类的委托功能再什么类当中进行使用,这里写成MyClass表示仅在MyClass类当中进行使用
  • 第二个参数KProperty<*>是Kotlin当中的一个属性操作类,可以用于获取各种属性相关的值
  • <*>这种泛型类的写法表示你不知道或者不关心泛型的具体类型,只是为了通过语法编译而已,有点类似于Java当中的<?>写法,只是为了t通过语法编译而已,至于返回值可以声明成任何类型,根据具体的实现逻辑去写就行
  • setValue()方法也是类似的,只不过他要接收三个参数,前两个参数和getValue()方法是相同的,最后一个参数表示具体给委托属性的值,这个参数的类型必须和getValue()方法返回值的类型保持一致.
  • 整个委托的工作流程是这样实现的,现在当我们给MyClass的p属性赋值的时候,就会调用Delegate类的setValue()方法,当获取MyClass中的p属性的值的时候,就会调用Delegate类的getValue()方法.
  • 还有一种情况可以不用在Delegate类中定义setValue()方法,那就是当我们给MyClass的p属性声明为val关键字的时候,因为p属性使用val关键字进行声明,那么就意味着p属性是无法在初始化之后再进行重新赋值的,因此也没有必要实现setValue()方法,只需要实现getValue()方法就可以了.

实现一个自己的lazy函数

  • lazy是kotlin当中的一种懒加载技术.把想要延迟执行的代码放到by lazy代码块当中,这样代码块在一开始就不会执行,只有当变量被首次调用的时候,代码块中的代码才会执行.
  • 事实上by laze …只有by才是Kotlin当中的关键字,lazy在这里只是一个高阶函数
  • 再lazy函数中会创建并且返回一个Delegate对象,当我们调用p属性的时候,其实调用Delegate对象的getValue()方法,然后getValue()方法中又会调用lazy函数传入的Lambda表达式,这样表达式中的代码就得到了执行,并且调用p属性后得到的值就是Lambda表达式中最后一行代码返回的值.
  • 新建一个Later.kt文件
import kotlin.reflect.KProperty

/**
 * @Description: 定义一个Later类,并将他指定成为泛型类型
 * Later的构造函数中接收一个函数类型参数,这个函数类型参数不接受任何参数
 * 并且返回值类型就是Later类指定的泛型类型
 * @Author zb~
 * @Date 2022/12/20 22:38
 */
class Later<T>(val block: () -> T) 
    var value: Any? = null
    operator fun getValue(any: Any?, prop: KProperty<*>): T 
        if (value == null) 
            value = block()
        
        return value as T
    

  • 这里将getValue()方法的第一个参数指定成为了Any?类型,表示我们希望Later的委托功能在所有类中都可以进行使用,然后使用一个value变量对值进行缓存,如果value为空就调用构造函数中传入的函数类型参数去获取值,否则就直接进行返回.
  • 由于懒加载技术是不会对属性进行赋值的,因此在这里我们就不用实现setValue()方法了
  • 代码写到这里,委托属性的功能就已经完成了,我们已经可以进行使用了,但是为了让他的用法更加类似于lazy函数,最好再定义一个顶层函数,这个函数直接写在Later.kt文件中就可以了,但是要定义在Later类的外面,因为不定义在任何类中的函数才是顶层函数,代码如下所示
/**
 * 定义一个泛型函数,作用是创建Later实例,并将接收函数类型参数传给Later类的构造函数
 * @param block Function0<T> 接收一个函数类型的参数
 * @return Later<T>
 */
fun <T> later(block: () -> T) = Later(block)
  • 现在我们自己编写的later懒加载函数就已经完成了,可以用它来直接代替lazy函数.

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

第三节:Java数据结构预备知识之泛型

kotlin修炼指南7之泛型

kotlin修炼指南7之泛型

C#委托之泛型

kotlin 实战之泛型与逆变协变总结

Java基础 -- 泛型之泛型参数