跳到主要内容

Kotlin 泛型

泛型是一种参数化类型的特性,允许在定义类、接口和函数时使用类型参数。Kotlin 的泛型系统强大而灵活,支持类型约束、协变、逆变等高级特性。

泛型基础

什么是泛型?

泛型让我们能够编写可以处理多种类型的代码,同时保持类型安全:

// 不使用泛型:类型不安全,需要强制转换
class Box {
private var item: Any? = null

fun put(item: Any) {
this.item = item
}

fun get(): Any? = item
}

fun main() {
val box = Box()
box.put("Hello")
val str: String = box.get() as String // 需要强制转换
}

// 使用泛型:类型安全,无需转换
class GenericBox<T>(private var item: T? = null) {
fun put(item: T) {
this.item = item
}

fun get(): T? = item
}

fun main() {
val stringBox = GenericBox<String>()
stringBox.put("Hello")
val str: String? = stringBox.get() // 无需转换,类型安全
}

泛型的优势

  1. 类型安全:编译时检查类型错误
  2. 代码复用:一套代码处理多种类型
  3. 消除转换:无需手动进行类型转换

泛型类

基本语法

// 定义泛型类
class Container<T>(var item: T) {
fun getItem(): T = item
fun setItem(newItem: T) {
item = newItem
}
}

fun main() {
// 显式指定类型
val intContainer: Container<Int> = Container(42)
val stringContainer: Container<String> = Container("Kotlin")

// 类型推断
val doubleContainer = Container(3.14) // 自动推断为 Container<Double>

println(intContainer.getItem()) // 42
println(stringContainer.getItem()) // Kotlin
}

多类型参数

// 键值对容器
class Pair<K, V>(val key: K, val value: V) {
fun printPair() {
println("Key: $key, Value: $value")
}

fun swap(): Pair<V, K> {
return Pair(value, key)
}
}

fun main() {
val pair = Pair("name", "Kotlin")
pair.printPair() // Key: name, Value: Kotlin

val swapped = pair.swap()
swapped.printPair() // Key: Kotlin, Value: name
}

泛型与继承

open class Animal(val name: String)
class Dog(name: String) : Animal(name)
class Cat(name: String) : Animal(name)

// 泛型容器
class Cage<T : Animal>(private val animal: T) {
fun getAnimal(): T = animal
fun getName(): String = animal.name
}

fun main() {
val dogCage = Cage(Dog("Buddy"))
val catCage = Cage(Cat("Whiskers"))

println(dogCage.getName()) // Buddy
println(catCage.getName()) // Whiskers
}

泛型函数

基本语法

// 定义泛型函数
fun <T> singletonList(item: T): List<T> {
return listOf(item)
}

// 多类型参数
fun <K, V> createMap(key: K, value: V): Map<K, V> {
return mapOf(key to value)
}

fun main() {
// 类型推断
val list1 = singletonList(1) // List<Int>
val list2 = singletonList("Hello") // List<String>

// 显式指定类型
val list3: List<Double> = singletonList(3.14)

val map = createMap("id", 12345)
println(map) // {id=12345}
}

泛型扩展函数

// 为 List 添加扩展函数
fun <T> List<T>.secondOrNull(): T? {
return if (size >= 2) this[1] else null
}

// 带约束的泛型扩展函数
fun <T : Number> List<T>.sumAll(): Double {
return this.sumOf { it.toDouble() }
}

// 链式调用扩展
fun <T> T.applyIf(condition: Boolean, block: T.() -> Unit): T {
if (condition) block()
return this
}

fun main() {
val list = listOf("a", "b", "c")
println(list.secondOrNull()) // b

val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.sumAll()) // 15.0

val result = "hello".applyIf(true) {
println("Processing: $this")
}
// 输出: Processing: hello
}

类型约束

上界约束

使用 : 指定类型参数必须继承的类或实现的接口:

// T 必须是 Number 或其子类
class NumberBox<T : Number>(val value: T) {
fun toDouble(): Double = value.toDouble()

fun isPositive(): Boolean = value.toDouble() > 0
}

fun main() {
val intBox = NumberBox(42) // OK
val doubleBox = NumberBox(3.14) // OK
// val stringBox = NumberBox("42") // 编译错误!

println(intBox.toDouble()) // 42.0
println(intBox.isPositive()) // true
}

多个约束

使用 where 子句指定多个约束:

// T 必须同时满足多个约束
fun <T> process(value: T) where T : CharSequence, T : Appendable {
if (!value.endsWith('.')) {
value.append('.')
}
println(value)
}

// 泛型类的多个约束
class Processor<T> where T : Runnable, T : Cloneable {
fun process(item: T) {
item.run()
}
}

约束的实际应用

// 确保类型可比较
fun <T : Comparable<T>> T.maxOf(other: T): T {
return if (this > other) this else other
}

// 确保类型可序列化
fun <T : java.io.Serializable> save(item: T): ByteArray {
// 序列化实现
return byteArrayOf()
}

fun main() {
println(5.maxOf(3)) // 5
println("b".maxOf("a")) // b
// println(true.maxOf(false)) // 编译错误!Boolean 不是 Comparable
}

协变与逆变

型变描述了具有相同基础类型但不同类型参数的泛型类型之间的关系。

不可变性

默认情况下,Kotlin 的泛型是不可变的:

class Container<T>(var item: T)

fun main() {
val intContainer: Container<Int> = Container(42)
// val anyContainer: Container<Any> = intContainer // 编译错误!
}

为什么不可变? 如果允许上述赋值,可能会导致类型安全问题:

// 假设可以赋值
val intContainer: Container<Int> = Container(42)
val anyContainer: Container<Any> = intContainer // 如果允许
anyContainer.item = "string" // 把 String 存入了 Int 容器!
val intValue: Int = intContainer.item // 运行时错误!

协变(out)

协变保留类型参数的子类型关系。使用 out 关键字声明:

// 协变:只能"生产" T,不能"消费" T
interface Producer<out T> {
fun produce(): T
}

class StringProducer : Producer<String> {
override fun produce(): String = "Hello"
}

fun main() {
// Producer<String> 是 Producer<Any> 的子类型
val producer: Producer<Any> = StringProducer() // 合法!
println(producer.produce()) // Hello
}

协变的限制:类型参数只能出现在输出位置(返回值):

// 正确:T 只出现在返回位置
interface Source<out T> {
fun next(): T
fun peek(): T
}

// 错误:T 不能出现在参数位置
// interface Producer<out T> {
// fun consume(item: T) // 编译错误!
// }

实际应用 - List 是协变的

fun printAll(list: List<Any>) {
list.forEach { println(it) }
}

fun main() {
val strings = listOf("Hello", "World")
val ints = listOf(1, 2, 3)

printAll(strings) // 合法!List<String> 是 List<Any> 的子类型
printAll(ints) // 合法!
}

逆变(in)

逆变反转类型参数的子类型关系。使用 in 关键字声明:

// 逆变:只能"消费" T,不能"生产" T
interface Consumer<in T> {
fun consume(item: T)
}

class AnyConsumer : Consumer<Any> {
override fun consume(item: Any) {
println("Consumed: $item")
}
}

fun main() {
// Consumer<Any> 是 Consumer<String> 的子类型
val consumer: Consumer<String> = AnyConsumer() // 合法!
consumer.consume("Hello") // Consumed: Hello
}

逆变的限制:类型参数只能出现在输入位置(参数):

// 正确:T 只出现在参数位置
interface Sink<in T> {
fun send(item: T)
}

// 错误:T 不能出现在返回位置
// interface Consumer<in T> {
// fun produce(): T // 编译错误!
// }

型变总结

型变类型关键字位置关系使用场景
协变out只读(返回值)保持子类型关系生产者
逆变in只写(参数)反转子类型关系消费者
不变读写都可无关系生产者+消费者

记忆口诀:Producer out, Consumer in(PECS 原则)

// 生产者使用 out
interface Producer<out T> {
fun produce(): T
}

// 消费者使用 in
interface Consumer<in T> {
fun consume(item: T)
}

// 既是生产者又是消费者,使用不变
class Box<T>(var item: T) {
fun get(): T = item // 生产
fun set(newItem: T) { // 消费
item = newItem
}
}

类型投影

类型投影是在使用时指定型变,而不是在定义时。

out 投影

// 复制数组:from 只能读取
fun <T> copy(from: Array<out T>, to: Array<in T>) {
for (i in from.indices) {
to[i] = from[i]
}
}

fun main() {
val strings = arrayOf("a", "b", "c")
val anys = arrayOfNulls<Any>(3)

copy(strings, anys) // 合法!
println(anys.contentToString()) // [a, b, c]
}

in 投影

// 填充数组:dest 只能写入
fun fill(dest: Array<in String>, value: String) {
for (i in dest.indices) {
dest[i] = value
}
}

fun main() {
val anyArray: Array<Any?> = arrayOfNulls(3)
fill(anyArray, "Hello")
println(anyArray.contentToString()) // [Hello, Hello, Hello]
}

星投影

当不知道具体类型参数时使用星投影:

fun main() {
// List<*> 等价于 List<out Any?>
val list: List<*> = listOf(1, 2, 3)

// 可以读取(返回 Any?)
val first: Any? = list[0]

// 不能写入(类型未知)
// list[0] = 4 // 编译错误

// Map<*, *> 键值类型都未知
val map: Map<*, *> = mapOf("a" to 1)

// 可以调用不依赖类型参数的方法
println(map.size)
println(map.keys) // 返回 Set<*>
println(map.values) // 返回 Collection<*>
}

泛型实化(reified)

由于 JVM 的类型擦除,泛型类型参数在运行时会被擦除。reified 关键字可以保留类型参数的运行时信息。

类型擦除问题

fun <T> checkType(value: Any) {
// if (value is T) { } // 编译错误!无法检查泛型类型
}

reified 关键字

// reified 只能与 inline 函数一起使用
inline fun <reified T> isType(value: Any): Boolean {
return value is T
}

inline fun <reified T> getClassName(): String {
return T::class.simpleName ?: "Unknown"
}

fun main() {
println(isType<String>("Hello")) // true
println(isType<Int>("Hello")) // false
println(getClassName<String>()) // String
println(getClassName<List<*>>()) // List
}

reified 实际应用

// 过滤特定类型的元素
inline fun <reified T> List<*>.filterIsInstance(): List<T> {
return filter { it is T } as List<T>
}

// 获取泛型的 Class 对象
inline fun <reified T> getJavaClass(): Class<T> = T::class.java

// JSON 解析示例
inline fun <reified T> parseJson(json: String): T {
// 实际实现需要 JSON 库
// return Json.decodeFromString<T>(json)
throw NotImplementedError("需要 JSON 库支持")
}

fun main() {
val mixed = listOf(1, "two", 3, "four", 5)
val strings = mixed.filterIsInstance<String>()
println(strings) // [two, four]

val ints = mixed.filterIsInstance<Int>()
println(ints) // [1, 3, 5]
}

reified 的限制

// ❌ 错误:reified 只能与 inline 函数一起使用
// fun <reified T> wrong() { }

// ❌ 错误:不能在类中使用
// class Box<reified T>

// ❌ 错误:不能用于非 inline 函数的参数默认值
// fun <reified T> process(default: T = T::class.java.newInstance())

泛型最佳实践

1. 合理命名类型参数

// 标准命名
class Box<T> // Type - 通用类型
class Map<K, V> // Key, Value - 键值对
class Comparator<T> // Element - 元素
class Function<P, R> // Param, Result - 参数和返回值

// 有意义的命名
class Repository<Entity, in Id : Comparable<Id>> {
fun findById(id: Id): Entity?
fun save(entity: Entity): Entity
}

2. 优先使用不可变和协变

// 推荐:只读接口使用协变
interface ReadOnlyRepository<out T> {
fun getAll(): List<T>
fun findById(id: Int): T?
}

// 推荐:只写接口使用逆变
interface WriteOnlyRepository<in T> {
fun save(item: T)
fun delete(item: T)
}

3. 提供有意义的约束

// 使用约束确保类型安全
fun <T : Comparable<T>> List<T>.sortedDescending(): List<T> {
return sortedWith(compareByDescending { it })
}

4. 避免 unchecked cast

// ❌ 不推荐:unchecked cast
fun <T> List<*>.cast(): List<T> {
return this as List<T> // unchecked cast
}

// ✅ 推荐:使用 reified 和安全检查
inline fun <reified T> List<*>.safeCast(): List<T>? {
return if (all { it is T }) this as List<T> else null
}

小结

本章我们学习了 Kotlin 泛型的核心内容:

  1. 泛型类和函数:创建类型安全的可复用代码
  2. 类型约束:限制类型参数的范围
  3. 协变(out):生产者使用,保持子类型关系
  4. 逆变(in):消费者使用,反转子类型关系
  5. 类型投影:使用时指定型变
  6. reified:保留泛型类型参数的运行时信息

Kotlin 的泛型系统比 Java 更安全、更灵活。理解型变是掌握 Kotlin 泛型的关键。

练习

  1. 创建一个泛型 Result<T> 类型,包含成功和失败两种状态
  2. 实现一个泛型缓存类,支持 getputremove 操作
  3. 使用协变实现一个只读的 Repository 接口
  4. 使用 reified 实现类型安全的 JSON 解析函数
  5. 实现 swap 函数,交换 MutableList 中两个元素的位置