开始
泛型类
1 | class MyClass<T> { |
泛型函数
1 | fun <T> method(t: T): T { |
泛型约束
用于约束泛型参数的上界,类似 java 的 extends,默认的上界是 Any?
1 | class Person<T : String> |
当类型需要多个上界时,使用 where 语句,T 类型必须同时继承/实现 A 类型和 B 类型
1 | class Person<T> where T : A, T : B |
类型擦除
在 Java 和 Kotlin 中都有类型擦除,指的是在使用泛型时,泛型参数会在编译期间去除,也就是 List<T>
的泛型参数 T 会被擦除,List<T>
就变成 List,类中使用到泛型参数的地方都会被赋予类型
随之带来一个问题,就是无法在运行时判断泛型参数的类型(Kotlin 可以通过 reified 关键字实现),同时无法描述泛型参数的类型关系,其中一个例子是 List<Object>
不是 List<String>
的父类,这样的现象称为不变
协变与逆变
协变和逆变用于解决不变问题,二者统称为型变
协变和逆变通常用在函数参数和返回值的类型声明上,用于扩展容器输入和输出的类型范围,并不能进行实际地转换,如果要实现类型转换,只能创建新对象,手动进行转换
星投影:类似 java 的泛型通配符 ?
,用于接收不确定的泛型信息,只能读取值(向外输出值),相当于 <out Any?>
-
Java 实现:使用 extends 实现协变,使用 super 实现逆变,只支持使用处型变
1
2
3
4
5
6
7
8
9interface Collection<T> {
// 协变:向下扩展类型范围,只能安全读取,确保读取的都是T的子类
Collection<? extends T> method1();
void method2(Collection<? extends T> c);
// 逆变:向上扩展类型范围,只能安全写入,确保写入的都是T的子类
Collection<? super T> method3();
void method4(Collection<? super T> c);
} -
Kotlin 实现:使用 out 实现协变,使用 in 实现逆变,支持声明处型变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 使用处型变
interface Collection<T> {
// 协变
fun method1(): Collection<out T>;
fun method2(c: Collection<out T>);
// 逆变
fun method3(): Collection<in T>;
fun method4(c: Collection<in T>);
}
// 声明处型变
interface Collection<out T> {
// T只支持协变
fun method1(): Collection<T>;
fun method2(c: Collection<T>);
}
interface Collection<in T> {
// T只支持逆变
fun method3(): Collection<in T>;
fun method4(c: Collection<in T>);
}
协变逆变原理
核心原理为一个父类引用可以无障碍地接收一个子类对象,协变和逆变都是基于这一点实现的
容器泛型使用协变还是逆变,与它作为形参还是返回值是无关的,要看该容器的值的使用场景
- 当容器在消费场景时,容器需要被读取,因此外部需要使用一个父类引用接收读取值,因此容器泛型使用协变声明,确保容器内的值都是外部声明类型的子类,可以安全接收
- 当容器在生产场景时,容器需要被写入,因此容器内需要使用一个父类引用接收写入值,因此容器泛型使用逆变声明,确保写入值都是容器声明类型的子类,可以安全写入
具体化泛型参数
用于解决获取泛型参数的运行时类型问题
获取泛型的运行时类型场景
- 序列化和反序列化
- 数据库操作
- 自定义注解
- 泛型类型检查
kotlin 和 java 在运行时会进行类型擦除,因此无法在运行时获取泛型的实际类型
- java 可以通过反射获取
- kotlin 使用 reified+inline 实现
1 | // inline+reified |