美文网首页
Android学习Kotlin之六、泛型-扩展函数

Android学习Kotlin之六、泛型-扩展函数

作者: 艾曼大山 | 来源:发表于2022-01-06 09:05 被阅读0次

    Kotlin泛型-扩展函数

    泛型的函数或者变量可以接收任何类型;扩展可以在不直接修改类定义的情况下增加类功能,扩展可以用于自定义类,也可以用于比如List、String, 以及Kotlin标准库里的其他类。和继承相似,扩展也能共享类行为,在你无法接触某个类定义,或者某个类没有使用open修饰符,导致你无法继承它时,扩
    展就是增加类功能的最好选择。

    Kotlin

    其它Kotlin文章
    Android学习Kotlin之一、常量-条件-函数-高阶函数
    Android学习Kotlin之二、Null安全 -字符串操作- 类型转换
    Android学习Kotlin之三、标准库函数-集合List-Set-Map
    Android学习Kotlin之四、定义类-初始化-继承
    Android学习Kotlin之五、对象-接口-抽象类
    Android学习Kotlin之六、泛型-扩展函数

    本编文章会讲到的知识点

    • 泛型
      • 定义泛型
      • 注意
      • 泛型函数
      • 多泛型参数
      • 泛型类型约束
      • vararg关键字
      • []操作符获取
      • out(协变) in(逆变)
      • out&in案例
      • reified
    • 扩展函数
      • 定义扩展函数
      • 泛型扩展函数
      • 扩展属性
      • infix关键字
      • 带接收者的函数字面量

    泛型

    定义泛型

    泛型类的构造函数可以接受任何类型。
    GenericityBox类指定的泛型参数由放在一对<> 里的字母T表示,T是个代表item类型的占位
    符。GenericityBox类接受任何类型的item作为主构造函数值(item: T),并将item值赋给
    同样是T类型的subject私有属性。

    /**
     * 定义泛型
     */
    class GenericityBox<T>(item:T){
        var able = false
        private var mSub:T = item
    
        fun getT(): T? {
            return mSub.takeIf { able }
        }
    
        fun <R> getTR(subi:(T)->R): R ?{
            return subi(mSub).takeIf { able }
        }
    }
    
    class Gen(var name:String,var age:Int)
    class Cup(val name:String)
    

    注意

    泛型参数通常用字母T (代表英文type)表示,当然,想用其他字母,甚至是英
    文单词都是可以的。不过,其他支持泛型的语言都在用这个约定俗成的T,所以
    建议你继续用它,这样写出的代码别人更容易理解。

    泛型函数

    泛型参数也可以用于函数,定义一个函数用于获取泛型。

    // 1.定义泛型类
    class GenericityBox<T>(item:T){
        var able = false
        private var mSub:T = item
        fun getT(): T? {
            return mSub.takeIf { able }
        }
    }
    
        // 2.使用泛型函数
        val box3:GenericityBox<Gen> = GenericityBox(Gen("小明",12))
        val box4:GenericityBox<Cup> = GenericityBox(Cup("小芳"))
    
        box3.getT()?.run {
            println("box3=$name")
        }
        box4.able = true
        box4.getT()?.run {
            println("box4=$name")
        }
    
    图片.png

    多泛型参数

    泛型函数或泛型类也可以有多个泛型参数。

     // 1. 定义泛型
    class GenericityBox<T>(item:T){
        var able = false
        private var mSub:T = item
    
        fun <R> getTR(subi:(T)->R): R ?{
            return subi(mSub).takeIf { able }
        }
    }
    
        // 2.使用多泛型参数
        val box5:GenericityBox<Gen> = GenericityBox(Gen("小明",16))
        box5.able = true
        val cup = box5.getTR {
            Cup(it.name)
        }
        println("cup=${cup?.name}")// 打印cup=小明
    

    泛型类型约束

    如果要确保GenericityBox里面只能装指定类型的物品,如Gen类型, 怎么办?

     // 1.泛型类型约束 指定类型为Gen
    class GenericityBox2<T:Gen>(item:T){
        private var mSub:T = item
        fun getName():String{
            return mSub.name
        }
    }
        //2.使用泛型类型约束
        val box6:GenericityBox2<Gen> = GenericityBox2(Gen("小鹏",18))
    //    val b:GenericityBox2<Cup> 报错 泛型指定为Gen
        println("box6=${box6.getName()}")//打印小鹏
    

    vararg关键字

    GenericityBox能存放任何类型的Gen实例,但一次只能放-一个,如果需要放入多个实
    例呢?

    // 1.定义一个指定类型的类,并且接收多个对象
    class GenericityBox3<T:Gen>(vararg item : T){
        var able = false
        var mSub: Array<out T> = item
        fun getT(index:Int):T{
            return mSub[index]
        }
    }
        //2.使用vararg关键字 传入多个Gen对象
        val box7:GenericityBox3<Gen> = GenericityBox3(Gen("小张",10),Gen("小六",12))
        println("box7=${box7.getT(1)}")//打印 Gen(name=小六, age=12)
    

    []操作符获取

    想要通过[ ]操作符取值,可以重载运算符函数get函数。

    // 1.重载运算符函数get函数
    class GenericityBox4<T:Gen>(vararg item : T){
        var mSub: Array<out T> = item
    
        operator fun get(index: Int) :T{
            return mSub[index]
        }
    }
    // 2.使用[]操作符取值
    val box8:GenericityBox4<Gen> = GenericityBox4(Gen("小张",10),Gen("小六",12))
    println("box8=${box8[0]}")
    

    out(协变) in(逆变)

    out(协变)

    out (协变),如果泛型类只将泛型类型作为函数的返回(输出),那么使用out,可以称之为生产类/接口,因为它主要是用来生产(produce) 指定的泛型对象。

    // 泛型只作为函数的输出
    interface ProductionOut<out T>{
        fun onOut():T
    }
    

    in(逆变)

    in (逆变),如果泛型类只将泛型类型作为函数的入参(输入),那么使用in,可以称之为消费者类/接口,因为它主要是用来消费(consume)指定的泛型对象。

    // 泛型只作为函数的入参
    interface ProductionIn<in T>{
        fun onIn(item:T)
    }
    

    Invariant(不变)

    如果既将泛型作为函数参数,又将泛型作为函数的输出,那就既不用in 也不用out。

    // 即是协变又是逆变
    interface ProductionOutIn<T>{
        fun onOut():T
        fun onIn(item:T)
    }
    

    out&in案例

    1.定义生产接口和消费接口,先不使用out和in

    interface OnProductionOut<T>{
        fun onOut():T
    }
    interface OnConsumerIn<in T>{
        fun onIn(item:T)
    }
    

    2.定义三个类 食物 快餐 汉堡,分别是集成关系

    open class Food
    open class FastFood:Food()
    class Burger:FastFood()
    
    • 使用out
      1.定义三个生产食物的类分别实现OnProductionOut类
    class FoodOut:OnProductionOut<Food>{
        override fun onOut(): Food {
            println("FoodOut")
            return Food()
        }
    }
    class FastFoodOut:OnProductionOut<FastFood>{
        override fun onOut(): FastFood {
            println("FastFoodOut")
            return FastFood()
        }
    }
    class BurgerOut:OnProductionOut<Burger>{
        override fun onOut(): Burger {
            println("BurgerOut")
            return Burger()
        }
    }
    

    2.赋值使用

    java中使用泛型初始化左侧定义的泛型一定要和右侧初始化的泛型一致,比如 List<A> = new List<B> 会报错,kotlin中也是一样

    val mProduction1 : OnProductionOut<Food> = FoodOut() // 不报错, FoodOut()定义的泛型是Food
    val mProduction2 : OnProductionOut<Food> = FastFoodOut() // 报错,FastFoodOut()定义的泛型是FastFood,而左侧定义类型是Food 
    val mProduction3 : OnProductionOut<Food> = BurgerOut()// 报错,BurgerOut()定义的泛型是Burger,而左侧定义类型是Food 
    
    报错
    3.修改接口添加out,再使用就不会报错了,因为子类泛型对象可以赋值给父类泛型对象
    // 协变
    interface OnProductionOut<T>{
        fun onOut():T
    }
    
    // 逆变
    interface OnConsumerIn<T>{
        fun onIn(item:T)
    }
    
    val mProduction1 : OnProductionOut<Food> = FoodOut()
    val mProduction2 : OnProductionOut<Food> = FastFoodOut()
    val mProduction3 : OnProductionOut<Food> = BurgerOut()
    
    使用out
    • 使用in
      1.定义三个消费者
    class FoodIn:OnConsumerIn<Food>{
        override fun onIn(item: Food) {
            println("FoodIn")
        }
    }
    
    class FastFoodIn :OnConsumerIn<FastFood> {
        override fun onIn(item: FastFood) {
            println("FastFoodIn")
        }
    }
    
    class BurgerIn:OnConsumerIn<Burger> {
        override fun onIn(item: Burger) {
            println("BurgerIn")
        }
    }
    

    2.使用

        val mConsumer1 : OnConsumerIn<Burger> = FoodIn()// 报错,左侧泛型和右侧泛型类型不一致
        val mConsumer2 : OnConsumerIn<Burger> = FastFoodIn()// 报错,左侧泛型和右侧泛型类型不一致
        val mConsumer3 : OnConsumerIn<Burger> = BurgerIn()// 不报错 左侧泛型和右侧泛型类型一致
    
    泛型类型报错

    3.修改接口添加in,再使用就不会报错了,因为父类泛型对象可以赋值给子类泛型对象

    interface OnConsumerIn<in T>{
        fun onIn(item:T)
    }
    val mConsumer1 : OnConsumerIn<Burger> = FoodIn()
    val mConsumer2 : OnConsumerIn<Burger> = FastFoodIn()
    val mConsumer3 : OnConsumerIn<Burger> = BurgerIn()
    

    reified

    有时候,你可能想知道某个泛型参数具体是什么类型,reified关 键字能帮你检查泛型参数类型。Kotlin不 允许对泛型参数T做类型检查,因为泛型参数类型会被类型擦除,也就是说,T的类型信息在运行时是不可知的,Java也有这样的规则。

    • 错误写法

    Kotlin不 允许对泛型参数T做类型检查,因为泛型参数类型会被类型擦除


    图片.png
    • 使用inline reified


      图片.png
    /**
     * 1.定义测试类
     */
    class GenericityBox5<T : ReifiedParent> {
    
        /**
         * 需求 从集合中随机一个对象 判断随机的对象是不是穿过来的类型 如果是就返回随机对象,否则就执行传过来的方法
         */
        inline fun <reified T> randomOrBackup(backup: () -> T): T {
            val mList: MutableList<ReifiedParent> = mutableListOf(
                ReifiedSon1("小明"),
                ReifiedSon2("小芳")
            )
            val random = mList.shuffled().first()
            println("random=$random")
            return if (random is T) {
                random
            } else {
                backup()
            }
        }
    
    }
    
    //2.使用
        val box9: GenericityBox5<ReifiedParent> = GenericityBox5()
        val bean = box9.randomOrBackup {
            println("其他操作。。。")
            ReifiedSon1("小明")
        }
        println("bean=$bean")
    
    • 随机的类型和泛型类型一致


      图片.png
    • 随机的类型和泛型类型不一致


      图片.png

    扩展函数

    定义扩展函数

    定义扩展函数和定义一般函数差不多,但有一点大不一样,除了函数定义,你还
    需要指定接受功能扩展的接收者类型,后面会说到。

    // 给字符串后面添加内容
    fun String.addS(s: String) = this + s
    
    // 给Any类添加一个函数
    fun Any.easyPrint() = println(this)
    
        // 使用扩展函数
        println("小明".addS("HHHHHH"))
        "郭襄".easyPrint()
        6666.easyPrint()
    
    
    图片.png

    泛型扩展函数

    新的泛型扩展函数不仅可以支持任何类型的接收者,还保留了接收者的类型信息,使用泛型类型后,扩展函数能够支持更多类型的接收者,适用范围更广了。

    // 泛型扩展函数 打印之后再把当前对象返回给调用者
    fun <T> T.easyPrint2(): T {
        println(this)
        return this
    }
    
    "杨过".easyPrint2().addS("爱小龙女").easyPrint()
    
    图片.png
    • let扩展函数

    泛型扩展函数在Kotlin标准库里随处可见,例如let函数,let函数被定义成了泛型
    扩展函数,所以能支持任何类型,它接收一-个lambda表达式,这个lambda表达
    式接收者T作为值参,返回的R-lambda表达式返回的任何新类型。

    图片.png

    扩展属性

    除了给类添加功能扩展函数外,你还可以给类定义扩展属性,给String类添加一个扩展,这个扩展属性可以统计字符串里有多少个元音字母。

    //定义扩展属性
    val String.numV
        get() = count { "abc".contains(it) }
    
    // 使用扩展属性
    "asdfgzxc".numV.easyPrint()// 打印2
    

    infix关键字

    infix关键字适用于有单个参数的扩展和类函数,可以让你以更简洁的语法调用函数,如果一个函数定义使用了infix关键字,那么调用它时,接收者和函数之间的点操作以及参数的一对括号都可以不要。

    // 定义一个可空并使用infix修饰的扩展函数
    infix fun String?.setName(d:String) = println(this ?: d)
    
    // 可空变量
        var name:String? = null
    // 普通使用
        name.setName("小笼包")
    // 省略使用
        name setName "贾玲"
    
    infix使用

    带接收者的函数字面量

    apply函数是如何做到支持接收者对象的隐式调用的。

    图片.png

    相关文章

      网友评论

          本文标题:Android学习Kotlin之六、泛型-扩展函数

          本文链接:https://www.haomeiwen.com/subject/lkavbltx.html