- 作者:老汪软件技巧
- 发表时间:2024-09-11 10:02
- 浏览量:
Kotlin 中的泛型(Generics)提供了一种让类、接口和函数可以根据类型参数进行操作的机制。它允许编写更加通用和可重用的代码,而不必限定在特定的类型上。Kotlin 的泛型机制与 Java 类似,但引入了一些 Kotlin 特有的功能,比如 型变(variance) 和 投影(projection) ,使得泛型的使用更加灵活。
以下是 Kotlin 泛型的详细讲解:
1. 泛型的基础概念
泛型允许我们定义类型参数,使代码能够在编译时处理多种数据类型。一个泛型类或函数的定义如下:
1.1 泛型类
你可以通过在类名后面使用尖括号 来定义一个泛型类。例如,下面是一个简单的泛型类 Box,它可以包含任何类型的值:
kotlin
复制代码
class Box<T>(val value: T)
val intBox = Box(1) // Box
val stringBox = Box("Hello") // Box
这里 T 是类型参数,表示 Box 可以容纳任何类型的数据。Box 的实例化将会推断具体的类型参数。
1.2 泛型函数
同样,你可以为函数定义泛型:
kotlin
复制代码
fun singletonList(item: T): List {
return listOf(item)
}
val intList = singletonList(1) // List
val stringList = singletonList("Hello") // List
泛型函数在函数名之前声明了类型参数 ,并且可以在函数体内使用这个类型。
2. 型变(Variance)
Kotlin 中的型变是泛型的一个重要概念,用于处理子类型关系。Kotlin 的型变比 Java 更加严格和安全。它分为三种类型:
2.1 协变(Covariance) :out 关键字
协变允许你将泛型类型声明为可以向父类方向兼容。用 out 关键字声明的泛型只能作为返回类型(输出),不能作为输入参数。
kotlin
复制代码
interface Producer<out T> {
fun produce(): T
}
class Animal
class Dog : Animal()
fun demo(producer: Producer<Animal>) {
val animal: Animal = producer.produce()
}
val dogProducer: Producer = object : Producer {
override fun produce(): Dog {
return Dog()
}
}
demo(dogProducer) // 由于 Producer 是协变的,它可以赋值给 Producer
通过 out,Producer 可以被当作 Producer 来使用。这确保了类型安全,同时允许更灵活的泛型使用。
2.2 逆变(Contravariance) :in 关键字
逆变允许你将泛型类型声明为可以向子类方向兼容。用 in 关键字声明的泛型只能作为输入参数,不能作为返回类型。
kotlin
复制代码
interface Consumer<in T> {
fun consume(item: T)
}
fun demo(consumer: Consumer<Dog>) {
consumer.consume(Dog())
}
val animalConsumer: Consumer = object : Consumer {
override fun consume(item: Animal) {
println("Consumed an animal")
}
}
demo(animalConsumer) // Consumer 可以用于 Consumer
通过 in,Consumer 可以用于 Consumer 的场景,因为 Consumer 可以消费 Dog 或任何 Animal 的子类。
2.3 不变(Invariant)
如果没有使用 out 或 in 关键字,泛型是不变的,这意味着子类型和父类型之间没有自动的兼容关系。
kotlin
复制代码
class Box<T>(val value: T)
val box: Box = Box(Dog()) // 错误,Box 不能赋值给 Box
3. 泛型约束
Kotlin 允许对泛型进行约束,以限制类型参数可以接受的类型。最常见的约束是使用 : Any 或特定的父类来限制类型。
3.1 限制类型参数
通过 where 关键字,你可以进一步限制类型参数可以接受的多个约束条件:
kotlin
复制代码
fun > sort(list: List<T>) {
// T 必须是 Comparable 的子类
}
fun copyWhenGreater(list: List<T>, threshold: T): List
where T : CharSequence, T : Comparable {
return list.filter { it > threshold }
}
这里的泛型 T 被限制为 Comparable 或者其他指定的接口或类。
4. 泛型类型投影(Type Projections)
Kotlin 提供了 类型投影 以进一步控制泛型的使用方式。类型投影分为:
4.1 协变投影(out projection)
当你只打算从集合中读取数据时,可以使用协变投影来限制只能获取值,而不能设置值:
kotlin
复制代码
fun printValues(list: List<out Number>) {
for (number in list) {
println(number)
}
}
List 表示该列表中的元素是 Number 或 Number 的子类型,但不能往里面添加任何数据。
4.2 逆变投影(in projection)
当你只打算将数据写入集合时,可以使用逆变投影来限制只能设置值,而不能读取值:
kotlin
复制代码
fun addTo(list: MutableList<in String>) {
list.add("Hello")
}
MutableList 表示你可以向列表中添加 String 或 String 的子类,但不能保证可以从中读取特定类型的数据。
5. 泛型擦除(Type Erasure)
与 Java 类似,Kotlin 的泛型也是在运行时被 类型擦除(Type Erasure) 的。这意味着在运行时,泛型类型信息会被丢弃。例如,List 和 List 在运行时都是 List。
不过,Kotlin 提供了 reified(具象化)类型参数,允许在内联函数中保留泛型的类型信息。使用 reified 可以避免类型擦除带来的问题:
kotlin
复制代码
inline fun <reified T> isOfType(value: Any): Boolean {
return value is T
}
println(isOfType("Hello")) // true
println(isOfType<Int>("Hello")) // false
6. 泛型实战场景总结
Kotlin 的泛型提供了灵活而强大的类型安全机制,通过 型变、类型约束 和 投影,你可以更好地控制泛型的行为,确保代码的可读性和可维护性。泛型让代码更加通用和可重用,同时保持类型安全。