Kotlin 泛型的高级特性

对泛型进行实化

泛型实化的函数必须是内联函数,即用 inline 关键字来修饰函数,在声明泛型的地方必须加上 reified 关键字来表示该泛型要进行实化:

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

上面的函数实现了获取泛型实际类型的功能。

泛型实化的应用

启动一个 Activity 通常的写法如下:

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

利用 Kotlin 的泛型实化功能可以编写如下代码:

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

由于 T 已经是一个被实化的泛型,因此 Intent 的第二个参数可以直接传入 T::class.java,现在启动 TestActivity 可以直接这样写:

startActivity<TestActivity>(context)

上述方法无法传参,这是可以借助高阶函数来解决:添加一个新的函数重载:

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

startActivity() 函数中增加了一个函数类型参数,并且它的函数类型是定义在 Intent 类当中的,现在可以在 Lambda 表达式中为 Intent 传参了:

startActivity<TestAcitivty>(context) {
putExtra("param1", "data")
putExtra("param2", 123)
}

泛型的协变

一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以称它为 in 位置,而它的返回值是输出数据的地方,因此可以称它为 out 位置。

假如定义了一个 MyClass<T> 的的泛型类,其中 A 是 B 的子类型,同时 MyClass<A> 又是 MyClass<B> 的子类型,那么我们就可以称 MyClassT 这个泛型上是协变的。

如何让 MyClass<A> 成为 MyClass<B> 的子类型呢?如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的。要实现这一点,则需要让 MyClass<T> 类中所有方法都不能接收 T 类型的参数,也就是 T 只能出现在 out 位置上,而不能出现在 in 位置上。

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

泛型 T 的声明前面加上了 out 关键字,现在 T 只能出现在 out 位置上而不能出现在 in 位置上,同时意味着 SimpleData 在泛型 T 上是协变的。

由于泛型 T 不能出现在 in 位置上,因此不能使用 set() 方法为 data 参数赋值,可以使用构造函数的方式类赋值。由于使用了 val 关键字,所以构造函数中的泛型 T 仍然只读的,因此这样写是合法且安全的。如果使用了 var 关键字,只要加上 private 修饰符,保证这个泛型 T 对于外部而言是不可修改的,那么就是合法的写法。

以下代码可以编译通过且没有任何安全隐患:

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)

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

fun handleMyData(data: SimpleData<Person>) {
val personData = data.get()
}

由于 SimpleData 类已经进行了协变声明,那么 SimpleData<Student> 就是 SimpleData<Person> 的子类,所以这里可以安全地向 handleMyData() 方法中传递参数。在 handleMyData() 方法中获取 SimpleData 封装的数据,虽然这里泛型声明的是 Person 类型,实际获得的会是一个 Student 实例,由于 PersonStudent 的父类,向上转型是完全安全的,所以这段代码没有任何问题。

Kotlin 已经默认给许多内置的 API 加上了协变声明,其中包括各种集合的类和接口。Kotlin 中的 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>
public operator fun get(index: Int): E
}

List 在泛型 E 上是协变的,原则上在声明了协变之后,泛型 E 只能出现在 out 位置上,在 contains() 方法中,泛型 E 仍然出现在 in 位置上,这本身是不合法的,但是 contains() 方法的目的非常明确,它只是为了判断当前集合中是否包含参数中传入的这个元素而不会修改当前集合中的内容,因此这种操作实质上又是安全的。泛型 E 前面加上 @UnsafeVariance 注解,编译器就会允许泛型 E 出现在 in 位置上了。

泛型的逆变

假如定义了一个 MyClass<T> 的泛型类,其中 A 是 B 的子类型,同时 MyClass<B> 又是 MyClass<A> 的子类型,那么我们就可以称 MyClassT 这个泛型上是逆变的。

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

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

逆变功能在 Kotlin 内置 API 中应用比较典型的就是 Comparable 的使用:

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

参考