在 Kotlin 中,我们经常听说协变和逆变这两个概念,它们涉及到了 Kotlin 泛型的特性。在本文中,我们将深入探讨协变和逆变的概念,以及它们在 Kotlin 中的应用。
泛型类型
首先,让我们回顾一下什么是泛型。泛型是一种参数化类型的机制,它可以让我们在定义类、接口或函数时,指定一种类型的占位符。通过使用泛型,我们可以创建通用的代码,以适应不同类型的数据。
在 Kotlin 中,泛型使用尖括号 < >
来指定。例如,我们可以定义一个列表类型 List<T>
,其中的 T
是一种未知的类型。
协变(Covariance)
协变是指类型之间的继承关系可以保持不变,也就是说,如果 A
是 B
的子类型,那么 List<A>
就是 List<B>
的子类型。在 Kotlin 中,我们可以使用 out
关键字来标记类型参数 T
,表示 T
是一个协变的类型参数。
我们可以定义一个只读的介于协变类型参数 T
的列表接口,例如:
interface List<out T> {
operator fun get(index: Int): T
}
通过使用 out
关键字,我们告诉编译器列表 List<T>
是协变的,它可以安全地处理具体类型 T
的子类型。
例如,假设有一个 Person
类和一个 Student
类,Student
继承自 Person
。我们可以使用协变的列表接口 List<out T>
作为方法的参数类型,如下所示:
fun printList(list: List<Person>) {
for (i in 0 until list.size) {
val person: Person = list[i]
println(person.name)
}
}
fun main() {
val students: List<Student> = listOf(Student("Alice"), Student("Bob"))
printList(students)
}
由于 List<T>
是协变的,我们可以将 List<Student>
类型的列表传递给接受 List<Person>
类型参数的函数。
逆变(Contravariance)
逆变是指类型之间的继承关系相反,也就是说,如果 A
是 B
的子类型,那么 Consumer<B>
是 Consumer<A>
的子类型。在 Kotlin 中,我们可以使用 in
关键字来标记类型参数 T
,表示 T
是一个逆变的类型参数。
我们可以定义一个接受逆变类型参数 T
的消费者接口,例如:
interface Consumer<in T> {
fun consume(item: T)
}
通过使用 in
关键字,我们告诉编译器消费者接口 Consumer<T>
是逆变的,它可以安全地处理具体类型 T
的父类型。
例如,假设有一个 Person
类和一个 Student
类,Student
继承自 Person
。我们可以使用逆变的消费者接口 Consumer<in T>
作为方法的参数类型,如下所示:
fun printNames(consumer: Consumer<Student>) {
val students: List<Student> = listOf(Student("Alice"), Student("Bob"))
for (student in students) {
consumer.consume(student)
}
}
fun main() {
val personConsumer: Consumer<Person> = object : Consumer<Person> {
override fun consume(item: Person) {
println(item.name)
}
}
printNames(personConsumer)
}
由于 Consumer<T>
是逆变的,我们可以将 Consumer<Person>
类型的消费者传递给接受 Consumer<Student>
类型参数的函数。
总结
协变和逆变是 Kotlin 泛型中的重要概念。通过使用 out
关键字,我们可以标记类型参数为协变,以处理具有继承关系的类型。通过使用 in
关键字,我们可以标记类型参数为逆变,以处理具有相反继承关系的类型。这些特性使得我们可以更加灵活地使用泛型,提高代码的可复用性。
希望本文能够帮助你理解 Kotlin 中的协变与逆变概念,并且在实际开发中应用它们。如有任何疑问或建议,请随时留言。
本文来自极简博客,作者:大师1,转载请注明原文链接:Kotlin 中的协变与逆变