深入理解Kotlin的泛型系统

前言

Kotlin 的泛型与 Java 一样,都是一种语法糖,只在源代码里出现,编译时会进行简单的字符串替换。

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

泛型(Generics)其实就是把类型参数化,真正的名字叫做 类型参数,它给强类型编程语言加入了更强的灵活性。在 Java 中,只要是有类型的 API 元素,都可以泛型化,也就是泛型类、泛型接口、泛型方法和泛型属性,泛型类和泛型接口可以统称为泛型类型。其中最重要的是泛型类型和泛型方法,Kotlin 泛型系统继承了 Java 泛型系统,同时添加了一些强化的地方。

实化泛型参数

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

<T> void someFunction(Class<T> clazz) {

//...

}

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

fun <T> someFunction() {

val clazz = T::class.java

}

泛型的协变、逆变

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

boolean addAll(Collection<? extends E> c);

void sort(Comparator<? super E> c);

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

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

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

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

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

「*」投影

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

泛型的注意点:

在java编程中类型系统最棘手的一部分是通配符类型。但是,在Kotlin编程中,是没有通配符,采用声明变化和类型投影来替代。

通配符的作用: 使用界限通配符增加API的灵活性。

在Java编程中一个常见的问题:

List<String> strs = new ArrayList<String>();

List<Object> objs = strs; // !!! 这里会导致一个问题,在Java中是禁止这样做的

objs.add(1); //将integer类型的数据添加到String 类型的列表中

String s = strs.get(0); // !!! ClassCastException异常 : Integer类型不能转成String

在Kotlin编程中:

一旦声明类型后,不能加入其它的类型数据,如下图所示。

总结

以上是 深入理解Kotlin的泛型系统 的全部内容, 来源链接: utcz.com/z/352139.html

回到顶部