返回

划破迷障,破译Kotlin中泛型型变之谜(实践篇)

Android

泛型型变是 Kotlin 中一个复杂且强大的概念,在理解 Kotlin 中的泛型时,你不可不理解泛型型变。

协变、逆变和不变

协变(Covariance)是指子类的类型可以代替父类的类型,例如 List<SubClass> 可以代替 List<SuperClass>。逆变(Contravariance)是指父类的类型可以代替子类的类型,例如 Consumer<SuperClass> 可以代替 Consumer<SubClass>。不变(Invariant)是指子类的类型不能代替父类的类型,父类的类型也不能代替子类的类型。例如 Map<String, SubClass> 不能代替 Map<String, SuperClass>

类型推断

Kotlin 具有强大的类型推断功能,因此你可以省略泛型类型参数,编译器会自动推断出类型。例如,以下代码中,List<String> 类型的变量 names 可以省略类型参数:

val names = listOf("Alice", "Bob", "Carol")

类型安全

泛型在 Kotlin 中提供了类型安全,这意味着你可以防止在运行时发生类型错误。例如,以下代码试图将一个 Int 类型的对象添加到一个 List<String> 类型的列表中,编译器会报错:

val names = listOf("Alice", "Bob", "Carol")
names.add(1) // Error: cannot add Int to List<String>

泛型接口

泛型接口可以声明泛型类型参数,这允许你创建具有特定类型的接口。例如,以下接口 List<T> 声明了一个泛型类型参数 T,表示列表中元素的类型:

interface List<T> {
    fun add(element: T)
    fun remove(element: T)
    fun get(index: Int): T
    fun size(): Int
}

通配符

通配符(Wildcard)可以让你在不指定具体类型的情况下使用泛型类型。例如,以下代码中的 List<?> 表示一个列表,其中 ? 表示列表中元素的类型是未知的:

val list: List<*> = listOf("Alice", "Bob", "Carol")

类型擦除

泛型在 Java 中被擦除,这意味着泛型类型参数在编译时被擦除,只留下原始类型。例如,以下代码中的 List<String> 类型的列表 names 在编译后将被转换为 List 类型的列表:

val names = listOf("Alice", "Bob", "Carol")

泛型型变在实践中的应用

泛型型变在实践中有许多应用,例如:

  • 协变列表:协变列表允许你将子类的对象添加到父类的列表中。例如,以下代码将 SubClass 类型的对象添加到 List<SuperClass> 类型的列表中:
val superClassList: List<SuperClass> = listOf()
val subClassList: List<SubClass> = listOf(SubClass())
superClassList.addAll(subClassList)
  • 逆变消费者:逆变消费者允许你将父类的对象传递给子类的消费者。例如,以下代码将 SuperClass 类型的对象传递给 Consumer<SubClass> 类型的消费者:
val consumer: Consumer<SubClass> = Consumer { println(it) }
val superClassObject: SuperClass = SuperClass()
consumer.accept(superClassObject)

泛型型变的难点

泛型型变是 Kotlin 中一个复杂的概念,理解起来可能有些困难。以下是一些常见的难点:

  • 协变和逆变的概念可能会让人混淆。
  • 类型推断有时可能不正确,导致编译器错误。
  • 泛型接口和通配符的使用可能会很复杂。
  • 类型擦除可能会导致运行时错误。

总结

泛型型变是 Kotlin 中一个强大且灵活的概念,可以让你编写更安全、更可重用的代码。理解泛型型变可以帮助你更好地理解 Kotlin 中的泛型。