【Kotlin 初学者】泛型简单剖析与使用

简介: 一、泛型使用 泛型,即 "参数化类型",将类型参数化,可以用在类,接口,函数上。 与 Java 一样,Kotlin 也提供泛型,为类型安全提供保证,消除类型强转的烦恼。

作者简介: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的 上界通配符


微信图片_20220525001000.png


 因为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)
}

微信图片_20220525001123.png


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())
}

微信图片_20220525001203.png


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.

微信图片_20220525001239.png


四、inline + reified


       不管是Java还是Kotlin泛型在运行时会被擦除,但Kotlin可以使用内联函数避免这种限制,内联函数的类型形参能够被实化。


       inline 函数除了可以提高性能,内联代码之外,另一种场景就是类型参数可以被实化。


       如果把函数声明成 inline 并且用 reified 标记类型参数,就可以实化该类型参数。

微信图片_20220525001316.png


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随机产生结果有两种分别是KotlinSJavaS

       产生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)
}



相关文章
|
6月前
|
安全 Java Kotlin
Kotlin泛型:灵活的类型参数化
Kotlin泛型:灵活的类型参数化
|
4月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
46 2
|
1月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
|
2月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
33 3
|
2月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
|
2月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
Kotlin教程笔记(30) - 泛型详解
29 0
|
2月前
|
存储 安全 Java
Kotlin教程笔记(30) - 泛型详解
本教程详细讲解了Kotlin中的泛型概念,包括协变、逆变、类型投影及泛型函数等内容。适合已有Java泛型基础的学习者,深入理解Kotlin泛型机制。快速学习者可参考“简洁”系列教程。
34 0
|
6月前
|
安全 Java 编译器
Android面试题之Java 泛型和Kotlin泛型
**Java泛型是JDK5引入的特性,用于编译时类型检查和安全。泛型擦除会在运行时移除类型参数,用Object或边界类型替换。这导致几个限制:不能直接创建泛型实例,不能使用instanceof,泛型数组与协变冲突,以及在静态上下文中的限制。通配符如<?>用于增强灵活性,<? extends T>只读,<? super T>只写。面试题涉及泛型原理和擦除机制。
43 3
Android面试题之Java 泛型和Kotlin泛型
|
Kotlin
Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
114 0
|
安全 Java 编译器
Kotlin 泛型 VS Java 泛型
Kotlin 泛型 VS Java 泛型
95 0