划破迷障,破译Kotlin中泛型型变之谜(实践篇)
2024-01-01 19:53:35
泛型型变是 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 中的泛型。