泛型类型简介
泛型类型提供了一种重用代码的方式,使数据类型可以作为变量使用。这样,我们就能创建可以操作不同类型对象的类、接口和方法,只要这些对象符合类型参数的要求。
在 Kotlin 中,泛型类型用 <T>
关键字定义,但 T
可以是任意字母或单词。
在本主题中,我们将深入探讨泛型编程,并介绍类型、子类型和变异性(variance)等概念。这些概念在像 Kotlin 这样的静态类型语言中非常重要,因为类型关系会在编译时检查,它们帮助保证类型安全,同时提供灵活性。
类型与子类型
类型:定义了一组有效的值及其对应的一组适当且一致的操作。
子类型:在层级关系中,子类型继承了超类型的所有特性(有效值和操作),但也可能添加额外的值或操作,或者对值进行限制。换句话说,子类型的每个值都是超类型的值,但反之不成立。
这种关系通常通过面向对象编程中的继承实现。
举例:
val integer: Int = 1
val number: Number = integer // Int 是 Number 的子类型
open class Animal
class Dog : Animal()
fun main() {
val dog: Dog = Dog()
var animal: Animal = dog // Dog 是 Animal 的子类型
}
变异性(Variance)
当我们使用泛型(特别是基于泛型的集合)时,情况会变得复杂。
假设 Int
是 Number
的子类型,考虑泛型类 Box<T>
:
Box<Int>
是不是Box<Number>
的子类型?Box<Dog>
是不是Box<Animal>
的子类型?
Kotlin 中泛型默认是不变的(invariant),因此这些都不是子类型关系,会导致类型不匹配的错误:
class Box<T>
val bd: Box<Animal> = Box<Dog>() // 错误
val bn: Box<Number> = Box<Int>() // 错误
这就是变异性的问题。
变异性的三种形式
不变(Invariant):默认模式。对于两种不同类型 A 和 B,
Class<A>
既不是Class<B>
的子类型,也不是超类型。协变(Covariant):保留类型子关系方向。若 A 是 B 的子类型,则
Class<A>
也是Class<B>
的子类型。通常适用于“输出”(只返回值)的位置,Kotlin 用out
关键字表示。逆变(Contravariant):类型子关系反转。若 A 是 B 的子类型,则
Class<B>
是Class<A>
的子类型。通常适用于“输入”(只接收值)的位置,Kotlin 用in
关键字表示。
不变示例(Invariant)
默认情况下泛型是不变的:
val mutableAnimalsFromDogs: MutableList<Animal> = mutableListOf<Dog>() // 错误
val boxOfAnimalsFromDogs: Box<Animal> = Box<Dog>() // 错误
因为既能读也能写,所以不允许这种类型转换。
协变示例(Covariance)
适用于只读场景,比如 Kotlin 的 List
是协变的:
val dogs: List<Dog> = listOf(Dog(), Dog())
val animals: List<Animal> = dogs // 允许,因为 List 是协变的(定义为 List<out T>)
如果定义自己的泛型类协变:
class Box<out T>
val dogBox: Box<Dog> = Box<Dog>()
val animalBox: Box<Animal> = dogBox // 允许,Box 是协变的
注意:用 out
修饰后,只能用 T 作为返回类型,不能作为函数参数类型。
逆变示例(Contravariance)
适用于只写场景,比如比较器 Comparator
:
interface Comparator<in T> {
fun compare(e1: T, e2: T): Int
}
val animalComparator: Comparator<Animal> = ...
val dogComparator: Comparator<Dog> = animalComparator // 允许,Comparator 是逆变的
用 in
修饰后,只能用 T 作为函数参数类型,不能作为返回类型。
使用地点变异(Use-site variance)
通过在使用泛型时添加 out
或 in
来实现变异:
fun copyAnimals(source: MutableList<out Animal>, destination: MutableList<in Animal>) {
destination.addAll(source)
}
val dogs: MutableList<Dog> = mutableListOf(Dog())
val animals: MutableList<Animal> = mutableListOf()
copyAnimals(dogs, animals)
这里 source
是协变的,允许读取;destination
是逆变的,允许写入。
星号投影(Star Projection)
当你不知道具体泛型类型,或者不关心具体类型时,用 *
代替类型参数:
class Box<T>(val item: T)
fun printItems(boxes: List<Box<*>>) {
for (box in boxes) {
println(box.item)
}
}
这表示 Box
可以持有任何类型。
总结与注意事项
默认泛型是不变的。
只用作输出(返回值)的类型参数应声明为协变(
out
)。只用作输入(函数参数)的类型参数应声明为逆变(
in
)。in
和out
的使用限制了类型参数在类中的使用方式,保证类型安全。错误使用可能导致运行时错误,需谨慎。
复杂示例
open class Animal {
fun feed() = println("The animal is fed")
}
class Dog : Animal() {
fun pet() = println("The dog is petted")
}
class Cat : Animal() {
fun ignore() = println("The cat ignores you")
}
class Box<in T, out R>(private var t: T, private val r: R) {
fun put(t: T) {
this.t = t
}
fun take(): R {
return r
}
}
fun main() {
val dogBox: Box<Animal, Dog> = Box(Dog(), Dog())
dogBox.put(Cat()) // 可以,因为 Cat 是 Animal 的子类型
val dog: Dog = dogBox.take() // 返回 Dog
val catBox: Box<Dog, Animal> = Box(Dog(), Cat())
// catBox.put(Cat()) // 错误,Cat 不是 Dog 的子类型
val animal: Animal = catBox.take() // 返回 Animal
}