Kotlin 的泛型系统

  1. 1. 实化泛型参数
  2. 2. 泛型的协变、逆变
  3. 3. 「*」投影

泛型是静态类型语言中不可缺少的一部分,Kotlin 的泛型定义和使用都类似 Java,但也有一些基于工程实践考虑的优化和改进。

实化泛型参数

在 Java 中经常会定义这种方法:

1
2
3
<T> void someFunction(Class<T> clazz) {
//...
}

这是因为 Java 的泛型擦除机制无法使用形如 T.class 来获取泛型的真实类型对象。但是在调用者看来,泛型却是实实在在的固定类型,所以这里借助 Kotlin 的内联函数 inline 可以实化泛型参数,在 Kotlin 中只需要这样:

1
2
3
fun <T> someFunction() {
val clazz = T::class.java
}

泛型的协变、逆变

在 Java 中,定义带泛型的参数时为了更好的匹配目标类型,有 ? extends Type 和 ? super Type 两种形式,以 List 接口中的定义为例:

1
2
boolean addAll(Collection<? extends E> c);
void sort(Comparator<? super E> c);

addAll 方法中,Collection 中的泛型被定义成接收类型参数 E 的子类,这是因为需要读取也就 c 的值,所以需要保证 c 是 Collection<E> 的子类;而 sort 方法中,则是需要类中的类型参数 E 能够被 Comparator 中的方法传入,所以也就需要保证 E 是 Comparator 类型参数的子类。

而 Kotlin 中,针对于这两种情况给了另外两个关键字:需要读取带泛型对象的值时,使用 out 来标记类型参数;需要传入类型参数的类型作为形参时,使用 in。

这两种关键字的命名的方向是不同的:Java 偏向于从原理的方向命名,而 Kotlin 的命名对于具体的使用场景更为直观。在 Kotlin 中,被 out 标记类型参数的类型称之为协变类型,它代表当 A 是 B 的子类时,C<A> 也能作为 C<B> 的子类使用;而被 in 标记类型参数的类型则相反,它代表当 A 是 B的子类时,C<B>C<A> 的子类。

从方法参数的使用上来说,Kotlin 和 Java 似乎没有什么不同,而不同的地方在于 Kotlin 可以将这种定义作用在类型定义上,官方称之为声明点变型;与之相对应的,像 Java 这种在方法参数上定义的被称为使用点变型。

声明点变型在类型的声明时定义了该类型参数是用在入参还是出参上,之后在这个类中所有用到的地方都会直接调用该类型的定义名称来使用该类型的协变或者逆变。而 Java 中需要在每次使用时来重复说明该处需要协变还是逆变。Kotlin 也可以进行使用点变型,只要和 Java 一样,在声明处不进行说明,而只在使用时声明就可以了。

「*」投影

因为 Kotlin 源码中不允许忽略泛型参数,所以在一些泛型不重要的地方,就不可避免的使用 来表示。当使用 时,为了保证类型安全,官方建议的模式是将泛型定义为 的对象封装起来,写操作一般是安全的,因为 可以接收一切类型;对于读操作可以进行安全的转换,对于不匹配的类型进行统一处理。