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

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容