Skip to content

泛型与型变 (Generics & Variance)

源:Kotlin 泛型参考指南

Kotlin 的泛型系统比 Java 更加严格且类型安全。核心难点在于理解 协变 (out)逆变 (in)

基础泛型

kotlin
class Box<T>(val item: T)

fun <T> singletonList(item: T): List<T> {
    return listOf(item)
}

型变 (Variance):out 与 in

在 Java 中,List<String> 不是 List<Object> 的子类型。这保证了类型安全,但也限制了灵活性。 Kotlin 使用 outin 关键字来解决这个问题。

记忆法则:PECS

Effective Java 提出的 PECS 原则 (Producer-Extends, Consumer-Super) 在 Kotlin 中完美对应:

  • Producer -> out: 只能 (作为返回值),不能存。
  • Consumer -> in: 只能 (作为参数),不能取。

1. 协变 (out) - 生产者

如果你希望 Source<Dog>Source<Animal> 的子类型的,你需要标记 T 为协变。 代价:T 只能出现在返回位置

kotlin
// 定义:interface List<out E>
interface Source<out T> {
    fun nextT(): T // ✅ 作为返回值
    // fun add(t: T) // ❌ 编译错误:不能作为参数
}

fun demo(dogs: Source<Dog>) {
    val animals: Source<Animal> = dogs // ✅ 子类泛型赋值给父类泛型
}

2. 逆变 (in) - 消费者

如果你希望 Comparable<Animal>Comparable<Dog> 的子类型(这是反直觉的,但逻辑正确:能比较所有动物的比较器,一定能比较狗),你需要标记 T 为逆变。 代价:T 只能出现在参数位置

kotlin
// 定义:interface Comparable<in T>
interface Consumer<in T> {
    fun consume(t: T) // ✅ 作为参数
    // fun produce(): T // ❌ 编译错误
}

fun demo(animalConsumer: Consumer<Animal>) {
    val dogConsumer: Consumer<Dog> = animalConsumer // ✅ 父类泛型赋值给子类泛型
    dogConsumer.consume(Dog())
}

泛型约束

默认泛型上界是 Any?

kotlin
// T 必须是非空的
fun <T : Any> checkNotNull(t: T) {}
kotlin
// T 必须同时继承 View 并实现 Checkable
fun <T> verify(view: T) where T : View, T : Checkable {
    view.isChecked = true // 同时拥有两者的 API
}

星号投影 (Star-projection)

不知道(或不关心)具体泛型类型,但想安全使用?使用 *

  • 对于 Foo<out T>: Foo<*> 相当于 Foo<out TUpper> (只读)
  • 对于 Foo<in T>: Foo<*> 相当于 Foo<in Nothing> (只写,也即啥都不能写)
kotlin
fun printList(list: List<*>) {
    // 可以读取为 Any?
    val item: Any? = list[0]
}

类型擦除与 Reified

JVM 运行时会擦除泛型信息。List<String> 在内存中只是 List。 如果你需要在函数体内部使用泛型类型 T(例如 p as TT::class),必须使用 内联函数 + reified

kotlin
inline fun <reified T> String.toObject(): T {
    // Gson 示例:这里能拿到 T 的真实 Class
    return Gson().fromJson(this, T::class.java)
}

val user: User = json.toObject()