Kotlin泛型-扩展函数
Kotlin泛型的函数或者变量可以接收任何类型;扩展可以在不直接修改类定义的情况下增加类功能,扩展可以用于自定义类,也可以用于比如List、String, 以及Kotlin标准库里的其他类。和继承相似,扩展也能共享类行为,在你无法接触某个类定义,或者某个类没有使用open修饰符,导致你无法继承它时,扩
展就是增加类功能的最好选择。
其它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扩展函数
图片.png泛型扩展函数在Kotlin标准库里随处可见,例如let函数,let函数被定义成了泛型
扩展函数,所以能支持任何类型,它接收一-个lambda表达式,这个lambda表达
式接收者T作为值参,返回的R-lambda表达式返回的任何新类型。
扩展属性
除了给类添加功能扩展函数外,你还可以给类定义扩展属性,给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使用
带接收者的函数字面量
图片.pngapply函数是如何做到支持接收者对象的隐式调用的。
网友评论