Kotlin基础十一:泛型

102 阅读4分钟

前言

一些编程语言Java C Swift引入了泛型。Kotlin中在用法上和它们都很类似。

泛型基础

泛型声明使用<T>语法,将<T>放在方法名前。也可以采用大写字母组合<VM> <VP>

泛型函数

fun test() {
    val res = getData<String>("test")
    // 类型推导,可省略类型参数
    val res2 = getData("test")
}
private fun <T> getData(data:T):T{
    return data
}

泛型约束

声明的泛型一个边界,<T extends Base>类似Java的

open class Person
class MiddlePerson:Person()
class YoungPerson:Person()
private fun <T:Person> getInfo(person:T):T{
    return person
}

如果没有声明,默认上界Any?,在尖括号中指定一个上界,如果需要多个上界,用Where子句

open class Person
private fun <T> getInfo(person:T):T where T:Person,T:Serializable{
    return person
}

泛型函数运用

泛化类型支持扩展函数,T.funName()一个泛化的类型添加了扩展函数

fun <T> T.call(block:()->Unit):T{
    block()
    return this
}

给一个函数类型添加接收者,在函数声明的时候在()前加上ClassName.即可

block:ClassName.()->Unit

给一个函数类型添加一个泛化的接收者,函数类型初始化的Lambda表达式中就会拥有实际调用类型的上下文作用域

block:T.()->Unit

apply()函数

  1. 可以使用任意的对象访问它
  2. apply()函数调用Lambda表达式拥有调用者上下文作用域
  3. apply()函数返回其调用者本身

apply函数

  1. 是一个泛化类型的扩展函数
  2. 拥有一个函数类型的参数,该函数类型拥有一个接收者,该接收者是泛化类型
  3. 返回值是泛化类型本身

apply()函数源码

inline fun <T> T.apply(block:T.()->Unit):T{
    block()
    return this
}

泛型类

class DataBean<T>{
    val code=0
    val message = "success"
    private var data:T? = null
    fun setData(data:T){this.data = data}
    fun  getData():T?{return data}
}

类上声明的类型参数,可作为类成员变量来使用,也可以将其作为方法的参数或者返回值

fun test() {
    val appVersionInfo = AppVersionInfo("00","1.0")
    val data = DataBean<AppVersionInfo>()
    data.setData(appVersionInfo)
}
class DataBean<T> {
    val code = 0

    val message = "success"

    private var data: T? = null

    fun setData(data: T) { this.data = data }

    fun getData() : T? { return data }
}
data class AppVersionInfo(val code:String,val name:String)

泛型接口

接口可以声明类型参数。

interface Factory<T>{
    fun create(carType:Int):T
}
// 密封类
sealed interface CarType
object SUV:CarType
object BUS:CarType
object Car:CarType

定义Fractory<T>泛型接口,内部定义了抽象方法create()接收具体的车型

fun test() {
    val factory = object : Factory<CarType>{
        override fun create(carType: Int): CarType {
            return when(carType){
                0->SUV
                1->SUV
                2->SUV
                else ->Car
            }
        }
    }
    val suv = factory.create(0)
    println(suv.javaClass.simpleName)
}
interface Factory<T>{
    fun create(carType:Int):T
}
// 密封类
sealed interface CarType
object SUV:CarType
object BUS:CarType
object Car:CarType
输出:
SUV

类型转换,向上转型是安全的,向下转型是不安全的

类型擦除机制

所有基于JVM语言,它们的泛型功能是通过类型擦除来实现。包括JAVA和Kotlin语言,在编译期间指定泛型约束,在运行时会被擦除。在编译期间声明两个存储不同数据类型和集合List<String>和List<Int>,运行期间JVM会把它们识别成List,JVM并不知道你在List中存放什么类型的数据。

reified关键字

Kotlin引入了内联函数,让泛型可以实现具体化的类型参数成为了可能。开发者可以使用T::class.java或者param as T语法。定义一个具体化的参数用关键字reified。声明一个具体化的类型参数:

  1. 具体化的类型参数只能在内联函数中声明
  2. 在声明泛型地方必须加上reified关键字来修饰
fun test() {
    val className = getSimpleName<String>()
    println(className)
}
inline fun <reified T> getSimpleName():String{
    return T::class.java.simpleName
}

上面泛型T是一个具体化的参数类型,Kotlin编译器会优化,通过Decompile查看反编译生成的java源码。内联函数中的代码会在编译时候自动被替换到调用它的地方。通过查看java源码看到代码被替换,泛型被替换成了String类型。实际上是Kotlin编译器在编译期间将我们的类型参数使用替换成了具体的类型来使用。这样运行期间不存在类型擦除,使用的就是一个普通的类型。

总结

"当你希望代码能够跨多个类工作时,使用泛型才有帮助",熟练掌握泛型,写代码架构上时很有帮助的。