作者简介:CSDN博客专家、华为云·云享专家认证
系列专栏:Kotlin 初学者
学习交流:三人行必有我师焉;择其善者而从之,其不善者而改之。
一、泛型使用
泛型,即 "参数化类型",将类型参数化,可以用在类,接口,函数上。
与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。
1.1 泛型的优点
- 类型安全:通用允许仅保留单一类型的对象。泛型不允许存储其他对象。
- 不需要类型转换:不需要对对象进行类型转换。
- 编译时间检查:在编译时检查泛型代码,以便在运行时避免任何问题。
1.2 泛型类
TFood类指定的泛型参数由放在一对<>里的字母T表示,T是个代表item类型的占位符。TFood类接受任何类型的item作为主构造函数值(item: T)。
//1、创建泛型类 class TFood<T>(item:T){ init { // //使用is来检查item的类型。 // if(item is TApple){ // //使用 as?先进行安全类型转换,然后使用?.避免空指针 // println((item as? TApple)?.price) // }else{ // println(item) // } //以上内容,可以用下面一行搞定。 println(if(item is TApple)((item as? TApple)?.price)else item) } } //2、创建传入的类 class TApple(var price :Int) //3、使用 fun main() { //传入Int类型 TFood(30)//30 //传入String类型 TFood("水果")//水果 //传入实体对象 TFood(TApple(13))//13 }
此处如果不适用泛型,那么根据传入不同的引用类型可能需要创建多个类来接收。
泛型参数通常用字母T(代表英文type)表示,当然也可以用其他字母表示。不过,其他支持泛型的语言都在用这个约定俗成的T,所以建议你继续用它,这样写出的代码别人更容易理解。
1.3 泛型函数
泛型参数也可以用于函数。
定义一个函数用于获取元素,当且仅当泛型类可用时,才能获取元素。
class TFood<T>(item:T){ var tem:T = item //添加泛型函数 fun getItem():T?{ return tem } //多泛型参数,<R>返回泛型类型 fun <R> getItem(itemFun: (T) -> R): R { return itemFun(tem) } } //传入String类型 var s = TFood("水果")//水果 //传入实体对象 var apple = TFood(TApple(13))//13 //使用泛型函数 s.getItem().run { println(this)//水果 } apple.getItem().run { println(this)//实例对象:TApple@3f3afe78 println(this?.price)//13 } //传入Type类型,返回Int类型 var intType = apple.getItem { //it:TApple it.price }
1.4 泛型接口
1.//定义泛型接口 interface IFoodEffect<T>{ fun effect(item:T) } //实现接口 class Banana:IFoodEffect<String>{ override fun effect(item: String) { println(item)//item } } //使用 Banana().effect("常食香蕉有益于大脑,预防神经疲劳,还有润肺止咳、防止便秘")
二、泛型类型约束
指定参数类型:我想让这个泛型类只能传入某种类型。
open class Vip(price:Int) class TApple(var price: Int): Vip(price) class TFood<T:Vip>(item: T) { ... }
T:Vip,这样写就表示这里只能传入Vip和其子类。这个就类似Java的 上界通配符
因为Int和String类型未继承Vip类,所以报错。
三、形变
- out(协变):它只能出现在函数的输出位置,只能作为返回类型,即生产者。
- in(逆变):它只能出现在函数的输入位置,作为参数,只能作为消费类型,即消费者。
- 默认(不变):如果泛型类既将泛型类型作为函数参数,又将泛型类型作为函数的输出,那么既不用out也不用in。
3.1 不变
泛型类型即作为输出又作为参数。
//不变 interface IUnchanged<T> { //可返回T fun originally(): T //可作为参数传入T fun originally(t:T) } class BigStore:IUnchanged<Fruit>{ override fun originally(): Fruit { return Fruit() } override fun originally(t: Fruit) { } } class SmallStore:IUnchanged<AppleHn>{ override fun originally(): AppleHn { return AppleHn() } override fun originally(t: AppleHn) { } } fun main() { println("-------------") var bs:IUnchanged<Fruit> = BigStore() println(bs) var ss:IUnchanged<AppleHn> = SmallStore() println(ss) }
3.2 out-协变
它只能出现在函数的输出位置,只能作为返回类型,即生产者。
作用:可以将子类泛型对象可以赋值给父类泛型对象。
//out interface IReturn<out T>{ fun effect():T } open class Fruit() class AppleHn():Fruit() //生产者 class FruitMarket:IReturn<Fruit>{ override fun effect(): Fruit { println("FruitMarket effect") return Fruit() } } class AppleHnMarket:IReturn<AppleHn>{ override fun effect(): AppleHn { println("AppleHnMarket effect") return AppleHn() } } fun main() { //out:可以将子类泛型对象(AppleHn)可以赋值给父类泛型对象(Fruit) var fm:IProduction<Fruit> = FruitMarket() println(fm.effect()) //am的引用类型是Fruit对象 //但是AppleHnMarket返回的是AppleHn对象。 //这里将AppleHn对象赋值给Fruit对象并返回。 var am:IProduction<Fruit> = AppleHnMarket() println(am.effect()) }
3.3 in-逆变
它只能出现在函数的输入位置,只能作为参数,即消费者。
作用:可以将父类泛型对象可以赋值给子类泛型对象。
//in interface IConsumer<in T>{ fun spend(t:T) } class Animal:IConsumer<Fruit>{ override fun spend(t: Fruit) { println("Animal spend Fruit") } } class People:IConsumer<AppleHn>{ override fun spend(t: AppleHn) { println("People spend AppleHn") } } fun main() { //in:可以将父类泛型对象(Fruit)可以赋值给子类泛型对象(AppleHn) var fca: IConsumer<AppleHn> = Animal() fca.spend(AppleHn()) println(fca) var fcp: IConsumer<AppleHn> = People() fcp.spend(AppleHn()) println(fcp) }1.
四、inline + reified
不管是Java还是Kotlin泛型在运行时会被擦除,但Kotlin可以使用内联函数避免这种限制,内联函数的类型形参能够被实化。
inline 函数除了可以提高性能,内联代码之外,另一种场景就是类型参数可以被实化。
如果把函数声明成 inline 并且用 reified 标记类型参数,就可以实化该类型参数。
class BookS<T : AndroidS> { // fun <T> readBookT(anonymous: () -> T): T { // var list = listOf( // KotlinS("Kotlin 初学者", 12), // JavaS("Java 帅次", 28) // ) // var data = list.shuffled().first() // return if (data is T) { // data // } else { // anonymous() // } // } //T的类型由anonymous()返回类型推断决定 inline fun <reified T> readBook(anonymous: () -> T): T { var list = listOf( KotlinS("Kotlin 初学者", 12), JavaS("Java 帅次", 28) ) var data = list.shuffled().first() println(data) //如果data是T则返回data,否则执行anonymous() return if (data is T) { data } else { anonymous() } } } open class AndroidS(name: String) class KotlinS(var name: String, var price: Int) : AndroidS(name){ override fun toString(): String { return "KotlinS(name='$name', price=$price)" } } class JavaS(var name: String, var price: Int) : AndroidS(name){ override fun toString(): String { return "JavaS(name='$name', price=$price)" } } fun main() { var bookS:BookS<AndroidS> = BookS() //由anonymous()推断决定T的类型,这里的T是KotlinS var data = bookS.readBook { KotlinS("Anonymous-K",23) } println(data) }
泛型T是KotlinS,list随机产生结果有两种分别是KotlinS和JavaS
产生KotlinS运行结果: 直接返回产生的KotlinS
1."E:\Android\Android StudioO\jre\bin\java.exe" KotlinS(name='Kotlin 初学者', price=12) KotlinS(name='Kotlin 初学者', price=12) Process finished with exit code 0
产生JavaS运行结果: 调用anonymous()函数返回KotlinS
"E:\Android\Android StudioO\jre\bin\java.exe" JavaS(name='Java 帅次', price=28) KotlinS(name='Anonymous-K', price=23) Process finished with exit code 0
五、vararg
泛型类一次只能放一个,如果需要放入多个实例呢?
class BookMany<T : AndroidMany>(vararg item: T) { var data: Array<out T> = item } open class AndroidMany(name: String) class KotlinMany(var name: String, var price: Int) : AndroidMany(name) { override fun toString(): String { return "KotlinS(name='$name', price=$price)" } } class JavaMany(var name: String, var price: Int) : AndroidMany(name) { override fun toString(): String { return "JavaS(name='$name', price=$price)" } } fun main() { var book = BookMany( KotlinMany("初学者", 18), KotlinMany("进阶者", 28), KotlinMany("终结者", 38), JavaMany("全面者", 35), ) println(book)//com.scc.kotlin.primary.classkotlin.BookMany@3d24753a }
添加完多个对象,如果直接打印book,那么获取到的是个BookMany对象,那么怎么获取book里面的的对象?
重载运算符函数get函数,通过[]
操作符取值。
class BookMany<T : AndroidMany>(vararg item: T) { var data: Array<out T> = item operator fun get(index:Int) = data[index] } fun main() { var book = BookMany( KotlinMany("初学者", 18), KotlinMany("进阶者", 28), KotlinMany("终结者", 38), JavaMany("全面者", 35), ) println(book)//com.scc.kotlin.primary.classkotlin.BookMany@3d24753a println(book[0])//KotlinS(name='初学者', price=18) println(book[2])//KotlinS(name='终结者', price=38) }