Kotlin泛型中的基本用法和Java中的泛型用法是大致相同的,因此也比较好理解。然而实际上,Kotlin在泛型方面还提供了不少特有的功能,接下来将进行介绍。

一、对泛型进行实化

泛型实化这个功能对于绝大多数Java程序员来讲是非常陌生的,因为Java中完全没有这个概念。而如果我们想要深刻地理解泛型实化,就要先解释一下Java的泛型擦除机制才行。
Java的泛型功能是通过类型擦除机制来实现的。什么意思呢?就是说泛型对于类型的约束只在编译时期存在,运行的时候仍然会按照JDK1.5之前的机制来运行,JVM是识别不出我们在代码中指定的泛型类型的。例如,假设我们创建一个List< String >集合,虽然在编译时期只能向集合中添加字符串类型的元素,但是在运行事情JVM并不知道它本来只打算包含哪张类型的元素,只能识别出来它是个List。

所有基于JVM的语言,他们的泛型功能都是通过类型擦除机制来实现的,其中当然也包括了Kotlin。这种机制使得我们不可能使用a is T或者T::class.java这样的语法,因为T的实际类型在运行的时候已经被擦除了。

然而不同的是,Kotlin提供了一个内联函数的概念。内联函数中的代码会在编译的时候自动被替换到调用它的地方,这样的话也就不存在什么泛型擦除的问题了,因为代码在编译之后会直接使用实际的类型来替代内联函数的泛型声明。其工作原理如图:

android kotlin高阶函数原理 kotlin高级特性_kotlin


内联函数的代码替换过程

最终代码会被替换成如图所示的样子:

android kotlin高阶函数原理 kotlin高级特性_kotlin_02

可以看到 ,bar()是一个带有泛型类型的内联函数,foo()函数调用了bar()函数,在代码编译之后,bar()函数中的代码将可以获得泛型的实际类型。
这就意味着,Kotlin中是可以将内联函数中的泛型进行实化的。
那么具体该怎么写才能将泛型实化呢?首先,该函数必须是内联函数才行,也就是要用inline关键字来修饰该函数。其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行实化
示例代码如下:

inline fun <reified T> getGenericType(){
}

上述函数中的泛型T就是一个被实化的泛型,因为它满足了内联函数和reified关键字这两个前提条件。接下来准备一个获取泛型实际类型的功能,代码如下所示:

inline fun <reified T> getGenericType()=T::class.java

这里实现了一个Java中完全不可能实现的功能:getGenericType()函数直接返回了当前指定泛型的实际类型。T.class这样的语法在Java中是不合法的,而在Kotlin中,借助泛型实化功能就可以使用T::class.java这样的语法了
现在我们就可以使用如下代码对getGenericType()函数进行测试:

fun main(){
    val result1 = getGenericType<String>()
    val result2 = getGenericType<Int>()
    println("result1 is $result1")
    println("result1 is $result2")
}

这里给getGenericType()函数指定了两种不同的泛型,由于getGenericType()函数会将指定泛型的具体类型返回,因此这里我们将返回的结果进行打印。

android kotlin高阶函数原理 kotlin高级特性_java_03


可以看到,如果泛型指定成了Int,就可以得到java.lang.Integer的类型,如果将泛型指定成了String,那么就可以得到java.lang.String的类型。

二、泛型实化的应用

泛型实化功能允许我们在泛型函数当中获得泛型的实际类型,这也就使得类似于a is T、T::class.java这样的语法成为了可能。比如启动一个Activity就可以这么写:

val intent=Intent(context,TestActivity::class.java)
context.startActivity(intent)

对于TestActivity::class.java这样的语法可以通过Kotlin的泛型实化功能使得我们有更好的选择。
新建一个reified.kt文件,在里面编写如下代码:

inline fun <reified T> startActivity(context:Context){
    val intent=Intent(context,T::class.java)
    context.startActivity(intent)
}

这里我们定义了一个startActivity()函数,该函数接收一个Context参数,并同时使用inline和reified关键字让泛型T成为了一个被实化的泛型。接下来Intent接收的第二个参数本来应该是一个具体的Activity的Class类型,但由于现在T已经是一个被实化的泛型了,因此这里我们可以直接传入T::class.java。最后调用context的startActivity()方法来完成Activity的启动。
现在如果我们想要启动TestActivity,只需要这样写

startActivity<TestActivity>(context)

Kotlin将能够识别出指定泛型的实际类型,并启动相应的Activity。
不过现在的startActivity()函数其实还是有问题的,因为通常在启用Activity的时候还可能会使用Intent附带一些参数,比如下面的写法:

val intent=Intent(context,TestActivity::class.java)
intent.putExtra("param1","data")
intent.putExtra("param2",123)
context.startActivity(intent)

而经过刚才的封装之后,我们就无法进行传参了。
解决这个问题只需要利用高阶函数即可。回到reified.kt文件当中,这里添加一个新的startActivity()函数重载,如下所示:

inline fun <reified T> startActivity(context:Context,block:Intent.()->Unit){
    val intent=Intent(context,T::class.java)
    intent.block()
    context.startActivity(intent)
}

可以看到,这次的startActivity()函数中增加了一个函数类型参数,并且它的函数类型是定义在Intent类当中的。当创建完Intent的实例之后,随即调用该函数类型参数,并把Intent的实例传入,这样调用startActivity()函数的时候就可以在Lambda表达式中为Intent传递参数了,如下所示:

startActivity<TestActivity>(context){
         putExtra("param1","data")
         putExtra("param2",124)
     }

三、泛型的协变

在开始学习协变和逆变之前,我们还得先了解一个约定。一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以称它为in位置,而它的返回值是输出数据的地方,因此可以称它为out位置,如图所示:

android kotlin高阶函数原理 kotlin高级特性_泛型_04


有了这个约定前提,我们就可以继续学习了。首先定义如下3个类:

open class Person(val name:String,val age:Int)
class Student(name:String,age:Int):Person(name,age)
class Teacher(name:String,age:Int):Person(name,age)

这里先定义了一个Person类,类中包含name和age这两个字段。然后又定义了Student和Teacher这两个类,让它们成为Person类的子类。
如果某个方法接收到一个List< Person >类型的参数,而我们传入一个List< Student >的实例,在Java中是不允许这么做的,因为List< String >不能成为List< Person >的子类,否则将可能存在类型转换的安全隐患
为什么会存在类型转换的安全隐患呢?下面我们通过一个具体的例子进行说明。这里自定义一个SimpleData类,代码如下所示:

class SimpleData<T>{
private var data:T?=null
fun set(t:T?){
data=t
}
fun get():T?{
return data
}
}

SimpleData是一个泛型类,它的内部封装了一个泛型data字段,调用set()方法可以给data字段赋值,调用get()方法可以获取data字段的值。
接着我们假设,如果编程语言允许向某个接收SimpleData< Person >参数的方法传入SimpleData< Student >的实例,那么如下代码就会是合法的:

fun main(){
val student=Student("Tom",19)
val data=SimpleData<Student>()
data.set(student)
handleSimpleData(data)//实际上这行代码会报错,这里假设它能编译通过
val studentData=data.get()
}
fun handleSimpleData(data:SimpleData<Person>){
val teacher=Teacher("Jack",35)
data.set(teacher)
}

在main()方法中,我们创建了一个Student的实例,并将它封装到SimpleData< Student >当中,然后将SimpleData< Student >作为参数传递给handleSimpleData()方法。但是handleSimpleData()方法接收的是一个SimpleData< Person >参数(这里假设可以编译通过),那么在handleSimpleData()方法中,我们就可以创建一个Teacher的实例,并用它来替换SimpleData< Person >参数中的原有数据。这种操作肯定是合法的,因为Teacher也是Person的子类,所以可以很安全地将Teacher的实例设置进去。
但是问题来了,回到main()方法中,我们调用SimpleData< Student >的get()方法来获取它内部封装的Student数据,可现在SimpleData< Student >中实际包含的却是一个Teacher的实例,那么必然产生类型转换异常。
所以为了杜绝这种安全隐患,Java中是不允许使用这种方式来传递参数的。换句话说,即使Student是Person的子类,SimpleData< Student >并不是SimpleData< Person >的子类。
问题的主要原因是我们再handleSimpleData()方法中向SimpleData< Person >里设置了一个Teacher的实例。如果SimpleData在泛型T上是只读的话,肯定就没有类型转换的安全隐患了,那么这个时候SimpleData< Student >可不可以成为SimpleData< Person >的子类呢?
这里我们就引出了泛型协变的定义了。假设定义了一个MyClass< T >的泛型类,其中A是B的子类型,同时MyClass< A >又是MyClass< B >的子类型,那么我们就可以称MyClass在T这个泛型是协变的
但是如何才能让MyClass< A >成为MyClass< B >的子类型呢?如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。而要实现这一点,则需要让MyClass< T >类的所有方法都不能接收T类型参数。也就是说,T只能出现在out位置上,而不能出现在in位置上
现在修改SimpleData类的代码,如下所示:

class simpleData<out T>(val data:T?) {
    fun get():T?{
        return data
    }
}

这里我们对SimpleData类进行了改造,在泛型T的声明前面加上out关键字。这就意味着现在T只能出现在out位置上,而不能出现在in位置上,同时也意味着SimpleData在泛型T上是协变的
由于泛型T不能出现在in位置上,因此我们也就不能使用set()方法为data参数赋值了,所以这里使用构造函数的方式来赋值。虽然构造函数中的泛型T也是在in位置,但是由于这里我们使用了val关键字,所以构造函数中的泛型T仍然是只读的,因此是合法且安全的。另外,即使我们使用了var关键字,但是只要给它加上private修饰符,保证这个泛型T对于外部而言是不可修改的,那么就是合法的。

fun handleMyData(data:simpleData<Person>){
    val personData=data.get()
}
fun main(){
    val student=Student("Tom",19)
    val data = simpleData<Student>(student)
    handleMyData(data)
    val studentData=data.get()

}

由于SimpleData类已经进行了协变声明,那么SimpleData< Student >自然就是SimpleData< Person >的子类了,所以这里可以安全地向handleMyData()方法中传递参数
然后在handleMyData()方法中去获取SimpleData封装的数据,虽然这里泛型声明的是Person类型,实际获得的会是一个Student的实例,但由于Person是Student的父类,向上转型是安全的。
Kotlin在许多内置的API加上了协变声明,其中就包括了各种集合的类与接口。Kotlin中的List本身就是只读的,如果你想要给List添加数据,需要使用MutableList才行。既然List是只读的,也就是说明他是可以协变的。List的简化版源码:

public interface List<out E> : Collection<E> {
    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>
    override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
    public operator fun get(index: Int): E
    public fun indexOf(element: @UnsafeVariance E): Int
    public fun lastIndexOf(element: @UnsafeVariance E): Int
    public fun listIterator(): ListIterator<E>
    public fun listIterator(index: Int): ListIterator<E>
    public fun subList(fromIndex: Int, toIndex: Int): List<E>
}

List在泛型E的前面加上了out关键字,说明List在泛型E上是协变的。原则上在声明了协变之后,泛型E就只能出现在out位置上,可是你会发现contains()方法中,泛型E仍然出现在了in位置上。这种写法本身是不合法的,因为in位置上出现了泛型E就意味着就有类型转换的安全隐患。但是contains()方法本身目的非常明确,他只是为了判断当前集合中是否包含参数传入的这个元素,并不会修改当前集合的内容,而且在泛型E的前面加上@UnsafeVariance 注解,这样编译器就会允许泛型E出现在in位置上了。

四、泛型的逆变

定义上,逆变和协变完全相反。假如定义了一个MyClass< T >的泛型类,其中A是B的子类型,同时MyClass< B >又是MyClass< A >的子类型,那么我们就可以称MyClass在T这个泛型上是逆变的。协变和逆变的区别如图所示:

android kotlin高阶函数原理 kotlin高级特性_kotlin_05


从直观上,逆变的规则有点奇怪,原本A是B的子类型,怎么MyClass< B >能反过来成为MyClass< A >的子类型呢?接下来我们通过一个例子举例:

先定义一个Transformer接口,用于执行一些转换操作,代码如下所示:

interface Transformer<T>{
fun transform(t:T):String
}

Transformer接口中声明了一个transform()方法,它接收一个T类型的参数,并且返回一个String类型的数据,这意味着参数T在经过transform()方法的转换后将会变成一个字符串。
我们对Transformer接口进行实现,代码如下所示:

fun main(){
  val trans=object : Transformer<Person> {
            override fun transform(t: Person): String {
                return "${t.name} ${t.age}"
            }
        }
        handleTransformer(trans)//这段代码会报错
}
 fun handleTransformer(trans: Transformer<Student>) {
        val student=Student("Tom",19)
        val result=trans.transform(student)
    }

首先我们在main()方法中编写了一个Transformer< Person >的匿名类实现,并通过transform()方法将传入的Person对象转换成了一个“姓名+年龄”拼接的字符串。而handleTransformer()方法中创建了一个Student对象,并调用参数的transform()方法将Student对象转换成了一个字符串。
但是实际上在调用handleTransformer()方法的时候却会提示语法错误,原因很简单,Transformer< Person >并不是Transformer< Student >的子类型。
修改Transformer接口中的代码,如下所示:

interface Transformer<in T>{
fun transform(t:T):String
}

这里我们在泛型T的声明前面加上了一个in关键字。这就意味着现在T只能出现在in位置上,而不能出现在out位置上,同时也意味着Transformer在泛型T上是逆变的。
这样我们修改了一下,此时Transformer< Person >已经成为了Transformer< Student >的子类型。
在Kotlin内置API的应用,比较典型的就是Comparable的使用。Comparable是一个用于比较两个对象大小的接口,其源码定义如下:

interface Comparable<in T>{
operator fun compareTo(other:T):Int
}

可以看到,Comparable在T这个泛型上就是逆变的,compareTo()方法则用于实现具体的比较逻辑。那么为什么要让Comparable接口是逆变的呢?如果我们使用Comparable< Person >实现了让两个Person对象比较大小的逻辑,那么用这段逻辑去比较两个Student对象的大小也成立,因此让Comparable< Person >成为Comparable< Student >的子类也合理。