开始

泛型类

1
2
3
class MyClass<T> {
fun method(t: T): T
}

泛型函数

1
2
3
fun <T> method(t: T): T {
// statements
}

泛型约束

用于约束泛型参数的上界,类似 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
    9
    interface 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
2
3
4
5
6
7
8
9
// inline+reified
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
// T 可以作为类型,用于检查和转换
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}