引言:那个让我困惑三天的编译错误
还记得刚学Kotlin泛型时,我写了这样一段代码:
kotlin
// 我以为这样可以...
val strings: List<String> = listOf("A", "B", "C")
val objects: List<Any> = strings // ❌ 编译错误!Type mismatch
// 但这样却可以?
val readOnlyStrings: List<String> = listOf("A", "B")
val readOnlyObjects: List<Any> = readOnlyStrings // ✅ 编译通过!
为什么第一个不行,第二个却可以? 这个问题困扰了我整整三天。后来才明白,这涉及到泛型的 型变(Variance) 概念。
再看这个场景:
kotlin
// 想写一个通用的筛选函数
fun <T> filterItems(items: List<T>): List<T> {
return items.filter { it is String } // ❌ 怎么判断类型?
}
// 运行时类型擦除,泛型信息丢失了
fun <T> createInstance(): T {
return T() // ❌ 编译错误:Cannot use T as reified type parameter
}
这些问题的答案就在Kotlin泛型的高级特性中:型变、具体化类型参数、类型边界。
今天,我们就来深入探索这些让泛型更强大、更安全的特性。
泛型基础回顾
什么是泛型
泛型允许我们编写可以处理多种类型的代码,同时保持类型安全:
kotlin
// 没有泛型:需要为每种类型写一个类
class IntBox(val value: Int)
class StringBox(val value: String)
class UserBox(val value: User)
// 使用泛型:一个类搞定所有类型
class Box<T>(val value: T)
val intBox = Box(42) // Box<Int>
val stringBox = Box("Hello") // Box<String>
val userBox = Box(User()) // Box<User>
泛型的类型擦除
Java和Kotlin都使用类型擦除来实现泛型:
kotlin
// 编译期
val stringList: List<String> = listOf("A", "B")
val intList: List<Int> = listOf(1, 2)
// 运行时:类型参数被擦除
// stringList和intList的运行时类型都是List,没有泛型参数信息
类型擦除的影响:
kotlin
// ❌ 不能在运行时检查泛型类型
fun <T> isListOf(value: Any): Boolean {
return value is List<T> // ❌ 编译错误:Cannot check for instance of erased type
}
// ❌ 不能创建泛型数组
fun <T> createArray(): Array<T> {
return Array<T>(10) { } // ❌ 编译错误
}
// ❌ 不能使用泛型类型的伴生对象
fun <T> getCompanion() {
val companion = T.Companion // ❌ 编译错误
}
型变(Variance):理解协变与逆变
为什么需要型变
先看一个问题:
kotlin
// String是Any的子类型
val str: String = "Hello"
val any: Any = str // ✅ 可以赋值
// 那么List<String>是List<Any>的子类型吗?
val stringList: List<String> = listOf("A", "B")
val anyList: List<Any> = stringList // 这样可以吗?
答案取决于List的型变设置。
不变(Invariant):默认行为
默认情况下,泛型类型是不变的:
kotlin
// MutableList是不变的
val stringList: MutableList<String> = mutableListOf("A", "B")
// val anyList: MutableList<Any> = stringList // ❌ 编译错误
// 为什么不能?因为可能破坏类型安全:
val anyList: MutableList<Any> = stringList // 假设允许
anyList.add(42) // 加入一个Int
val first: String = stringList[0] // 💥 运行时错误!

**不变性保证类型安全**:如果允许`MutableList`赋值给`MutableList`,就可能向字符串列表中添加非字符串元素,破坏类型安全。
协变(Covariant):out关键字
协变 意味着:如果A是B的子类型,那么Producer<A>也是Producer<B>的子类型。
kotlin
// List是协变的(只读)
interface List<out T> { // out表示协变
fun get(index: Int): T // 只能读取(生产)T
// fun add(element: T) // ❌ 不能有接收T的方法
}
// 使用协变
val stringList: List<String> = listOf("A", "B")
val anyList: List<Any> = stringList // ✅ 可以赋值
val item: Any = anyList[0] // 只能读取,安全
协变的规则:
- 使用
out关键字声明 - 泛型类型只能出现在输出位置(返回值)
- 不能出现在输入位置(参数)
kotlin
// 协变类型示例
interface Producer<out T> {
fun produce(): T // ✅ 输出位置
// fun consume(item: T) // ❌ 输入位置,不允许
}
// 实际应用
class FruitBasket<out T : Fruit>(private val items: List<T>) {
fun pick(): T = items.random() // ✅ 只生产
// fun add(fruit: T) { } // ❌ 不能消费
}
val appleBasket: FruitBasket<Apple> = FruitBasket(listOf(Apple()))
val fruitBasket: FruitBasket<Fruit> = appleBasket // ✅ 协变
val fruit: Fruit = fruitBasket.pick() // 安全
逆变(Contravariant):in关键字
逆变 意味着:如果A是B的子类型,那么Consumer<B>是Consumer<A>的子类型(反过来)。
kotlin
// Comparator是逆变的
interface Comparator<in T> { // in表示逆变
fun compare(a: T, b: T): Int // 只能接收(消费)T
}
// 使用逆变
val anyComparator: Comparator<Any> = Comparator { a, b ->
a.hashCode() - b.hashCode()
}
val stringComparator: Comparator<String> = anyComparator // ✅ 可以赋值
// 为什么可以?
// 能比较Any的,当然也能比较String(String是Any的子类型)
逆变的规则:
- 使用
in关键字声明 - 泛型类型只能出现在输入位置(参数)
- 不能出现在输出位置(返回值)
kotlin
// 逆变类型示例
interface Consumer<in T> {
fun consume(item: T) // ✅ 输入位置
// fun produce(): T // ❌ 输出位置,不允许
}
// 实际应用
interface Sink<in T> {
fun send(item: T)
}
class AnySink : Sink<Any> {
override fun send(item: Any) {
println("Received: $item")
}
}
val anySink: Sink<Any> = AnySink()
val stringSink: Sink<String> = anySink // ✅ 逆变
stringSink.send("Hello") // 安全:Any的Sink可以接收String
型变总结

| 型变类型 | 关键字 | 类型关系 | 使用场景 | 示例 |
|---|---|---|---|---|
| 不变 | 无 | 无子类型关系 | 既读又写 | MutableList<T> |
| 协变 | out |
保持子类型关系 | 只读(生产者) | List<out T> |
| 逆变 | in |
反转子类型关系 | 只写(消费者) | Comparator<in T> |
**记忆口诀**: - **out** = 只**out**put(输出),生产者,保持方向 - **in** = 只**in**put(输入),消费者,反转方向
使用处型变(Use-site Variance)
有时我们不能修改类的声明,但想在使用时指定型变:
类型投影
kotlin
// Array是不变的
val strings: Array<String> = arrayOf("A", "B")
// val objects: Array<Any> = strings // ❌ 不变,不能赋值
// 使用out投影:只能读取
fun printArray(array: Array<out Any>) { // 使用处协变
for (item in array) {
println(item) // ✅ 可以读取
}
// array[0] = "X" // ❌ 不能写入
}
printArray(strings) // ✅ 可以传入Array<String>
// 使用in投影:只能写入
fun fillArray(array: Array<in String>, value: String) { // 使用处逆变
array[0] = value // ✅ 可以写入
// val item: String = array[0] // ❌ 不能读取(返回Any?)
}
val objects: Array<Any> = arrayOf("X", "Y")
fillArray(objects, "Hello") // ✅ 可以传入Array<Any>
星投影(Star Projection)
当你不关心具体类型时,可以使用星投影*:
kotlin
// 等价于out Any?
fun printList(list: List<*>) { // List<*> ≈ List<out Any?>
for (item in list) {
println(item) // item是Any?类型
}
}
printList(listOf(1, 2, 3))
printList(listOf("A", "B"))
// MutableList<*>的限制
fun processArray(array: Array<*>) {
val item: Any? = array[0] // ✅ 可以读取为Any?
// array[0] = "X" // ❌ 不能写入
}
星投影的规则:
Foo<*>≈Foo<out Any?>(如果是协变)Foo<*>≈Foo<in Nothing>(如果是逆变)Foo<*>= 既不能读也不能写(如果是不变)
具体化类型参数(Reified Type Parameters)
问题:类型擦除的限制
由于类型擦除,我们不能在运行时检查泛型类型:
kotlin
// ❌ 不能检查泛型类型
fun <T> isInstance(value: Any): Boolean {
return value is T // ❌ 编译错误:Cannot check for instance of erased type
}
// ❌ 不能获取泛型的Class
fun <T> getClassName(): String {
return T::class.simpleName // ❌ 编译错误
}
解决方案:reified关键字
Kotlin的reified关键字配合inline函数,可以保留类型信息:
kotlin
// ✅ 使用reified
inline fun <reified T> isInstance(value: Any): Boolean {
return value is T // ✅ 可以检查类型
}
// 使用示例
println(isInstance<String>("Hello")) // true
println(isInstance<Int>("Hello")) // false
// ✅ 获取Class对象
inline fun <reified T> getClassName(): String {
return T::class.simpleName ?: "Unknown"
}
println(getClassName<String>()) // "String"
println(getClassName<List<Int>>()) // "List"
工作原理:
kotlin
// 调用代码
isInstance<String>("Hello")
// 编译器内联后
"Hello" is String // 类型信息被保留
reified的实际应用
1. 类型安全的筛选
kotlin
// 筛选特定类型的元素
inline fun <reified T> List<*>.filterIsInstance(): List<T> {
return filter { it is T }.map { it as T }
}
val mixed: List<Any> = listOf(1, "two", 3, "four", 5.0)
val strings: List<String> = mixed.filterIsInstance<String>()
println(strings) // [two, four]
val numbers: List<Int> = mixed.filterIsInstance<Int>()
println(numbers) // [1, 3]
2. JSON反序列化
kotlin
// Gson的类型安全包装
inline fun <reified T> Gson.fromJson(json: String): T {
return fromJson(json, T::class.java)
}
val user: User = gson.fromJson<User>(jsonString) // 类型安全
// 而不是:
// val user = gson.fromJson(jsonString, User::class.java) as User
3. 启动Activity(Android)
kotlin
// Android中的类型安全Intent
inline fun <reified T : Activity> Context.startActivity() {
val intent = Intent(this, T::class.java)
startActivity(intent)
}
// 使用
startActivity<MainActivity>() // 简洁且类型安全
4. 依赖注入
kotlin
// 类型安全的依赖获取
inline fun <reified T> get(): T {
return container.resolve(T::class.java)
}
val userService: UserService = get<UserService>()
**reified的限制**: - 只能用于`inline`函数 - 不能用于类的类型参数 - 不能用于属性 - 会增加生成的字节码大小(内联的代价)
泛型边界(Generic Bounds)
上界(Upper Bounds)
限制类型参数必须是某个类型的子类型:
kotlin
// 单个上界
fun <T : Number> sum(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}
sum(1, 2) // ✅ Int是Number的子类型
sum(1.5, 2.5) // ✅ Double是Number的子类型
// sum("1", "2") // ❌ String不是Number的子类型
// 实际应用:比较
fun <T : Comparable<T>> max(a: T, b: T): T {
return if (a > b) a else b
}
println(max(10, 20)) // 20
println(max("apple", "banana")) // banana
多个上界
有时需要同时满足多个约束:
kotlin
// where子句指定多个上界
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<T>
where T : Comparable<T>, // 必须可比较
T : Cloneable { // 必须可克隆
return list.filter { it > threshold }.map { it.clone() as T }
}
// 实际应用
interface Named {
val name: String
}
interface Identifiable {
val id: String
}
fun <T> findById(items: List<T>, id: String): T?
where T : Named,
T : Identifiable {
return items.find { it.id == id }
}
data class User(
override val id: String,
override val name: String
) : Named, Identifiable
val users = listOf(User("1", "Alice"), User("2", "Bob"))
val user = findById(users, "1")
println(user?.name) // Alice
递归泛型边界
经典的Comparable模式:
kotlin
// 自引用泛型边界
interface Comparable<T : Comparable<T>> {
fun compareTo(other: T): Int
}
// 实现
class Person(val name: String, val age: Int) : Comparable<Person> {
override fun compareTo(other: Person): Int {
return age.compareTo(other.age)
}
}
// 使用
fun <T : Comparable<T>> sortItems(items: List<T>): List<T> {
return items.sorted()
}
val people = listOf(Person("Alice", 30), Person("Bob", 25))
val sorted = sortItems(people) // 按年龄排序

实战案例:构建类型安全的Result容器
综合运用泛型的高级特性,构建一个完整的Result类型:
kotlin
// 1. 基础定义:使用out协变
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// 2. 扩展函数:使用reified
inline fun <reified T> Result<*>.getOrNull(): T? {
return when (this) {
is Result.Success<*> -> data as? T
else -> null
}
}
// 3. map函数:协变保证类型安全
inline fun <T, R> Result<T>.map(transform: (T) -> R): Result<R> {
return when (this) {
is Result.Success -> Result.Success(transform(data))
is Result.Error -> Result.Error(exception)
is Result.Loading -> Result.Loading
}
}
// 4. flatMap:处理嵌套Result
inline fun <T, R> Result<T>.flatMap(transform: (T) -> Result<R>): Result<R> {
return when (this) {
is Result.Success -> transform(data)
is Result.Error -> Result.Error(exception)
is Result.Loading -> Result.Loading
}
}
// 5. 带边界的转换
inline fun <T : Any, R : Any> Result<T>.mapNotNull(
transform: (T) -> R?
): Result<R> {
return when (this) {
is Result.Success -> {
val transformed = transform(data)
if (transformed != null) {
Result.Success(transformed)
} else {
Result.Error(NullPointerException("Transform returned null"))
}
}
is Result.Error -> Result.Error(exception)
is Result.Loading -> Result.Loading
}
}
// 6. 合并多个Result
fun <T> combineResults(results: List<Result<T>>): Result<List<T>> {
val data = mutableListOf<T>()
for (result in results) {
when (result) {
is Result.Success -> data.add(result.data)
is Result.Error -> return result
is Result.Loading -> return Result.Loading
}
}
return Result.Success(data)
}
// 使用示例
class UserRepository {
suspend fun loadUser(id: String): Result<User> {
return try {
val user = apiService.getUser(id)
Result.Success(user)
} catch (e: Exception) {
Result.Error(e)
}
}
suspend fun loadUserProfile(id: String): Result<UserProfile> {
return loadUser(id)
.map { user -> user.toProfile() } // 类型安全转换
.flatMap { profile -> // 处理嵌套异步操作
loadAdditionalData(profile.id).map { data ->
profile.copy(additionalInfo = data)
}
}
}
}
// ViewModel中使用
class UserViewModel : ViewModel() {
private val repository = UserRepository()
val userState = MutableStateFlow<Result<UserProfile>>(Result.Loading)
fun loadUserProfile(userId: String) {
viewModelScope.launch {
val result = repository.loadUserProfile(userId)
// 使用reified扩展
val profile: UserProfile? = result.getOrNull<UserProfile>()
userState.value = result
}
}
}
这个案例展示了:
- ✅
out协变保证类型安全的转换 - ✅
reified实现类型安全的值获取 - ✅ 泛型函数实现灵活的操作符
- ✅ 密封类结合泛型的强大表达能力
常见问题解答
Q1: 什么时候使用out,什么时候使用in?
A : 遵循PECS原则(Producer Extends, Consumer Super):
kotlin
// Producer(生产者)使用out
interface Producer<out T> {
fun produce(): T // 只生产T
}
// Consumer(消费者)使用in
interface Consumer<in T> {
fun consume(item: T) // 只消费T
}
// 实际应用
fun copy(from: List<out Any>, to: MutableList<in String>) {
// from是生产者,只读取
// to是消费者,只写入
for (item in from) {
if (item is String) {
to.add(item)
}
}
}
Q2: reified一定要配合inline使用吗?
A : 是的,reified必须用于inline函数:
kotlin
// ✅ 正确:inline + reified
inline fun <reified T> check(value: Any): Boolean {
return value is T
}
// ❌ 错误:不能单独使用reified
fun <reified T> check(value: Any): Boolean { // 编译错误
return value is T
}
// 原因:只有内联函数才能在编译时替换类型参数
为什么:
inline函数在编译时会被展开到调用处- 编译器可以在展开时替换类型参数为具体类型
- 非内联函数在运行时调用,类型信息已被擦除
Q3: 星投影什么时候使用?
A: 当你不关心具体类型,只需要访问与类型无关的成员时:
kotlin
// 场景1:只需要知道是个List,不关心元素类型
fun printSize(list: List<*>) {
println("Size: ${list.size}") // size与类型无关
}
// 场景2:类型不确定但需要处理
fun processUnknown(value: Any) {
when (value) {
is List<*> -> println("List of ${value.size} items")
is Map<*, *> -> println("Map of ${value.size} entries")
}
}
// ❌ 不要这样:明确知道类型时不要用星投影
fun printStrings(list: List<*>) { // 不好
for (item in list) {
println(item as String) // 需要强制转换
}
}
// ✅ 应该这样:明确类型
fun printStrings(list: List<String>) { // 好
for (item in list) {
println(item) // 类型安全
}
}
Q4: 为什么Array是不变的,而List可以协变?
A : 因为可变性:
kotlin
// Array是可变的(可读可写)
val array: Array<String> = arrayOf("A", "B")
array[0] = "X" // 可以修改
// 如果允许协变,会破坏类型安全:
// val objects: Array<Any> = array // 假设允许
// objects[0] = 42 // 加入Int
// val str: String = array[0] // 💥 运行时错误
// List是只读的(协变安全)
val list: List<String> = listOf("A", "B")
// list[0] = "X" // ❌ 不能修改
val objects: List<Any> = list // ✅ 安全,因为不能修改
规则:
- 只读集合 (
List,Set,Map)可以协变 - 可变集合 (
MutableList,MutableSet,MutableMap)必须不变
练习题
练习1:实现泛型栈
kotlin
// 实现一个类型安全的栈
class Stack<T> {
private val items = mutableListOf<T>()
fun push(item: T) { /* TODO */ }
fun pop(): T? { /* TODO */ }
fun peek(): T? { /* TODO */ }
fun isEmpty(): Boolean { /* TODO */ }
}
// TODO: 添加协变的只读视图
// interface ReadOnlyStack<out T> { ... }
练习2:带边界的查找函数
kotlin
// 实现一个通用的查找函数,要求:
// 1. T必须实现Comparable接口
// 2. T必须有name属性
// 3. 查找name匹配且值大于threshold的第一个元素
// TODO: 实现函数签名和函数体
练习3:使用reified简化反序列化
kotlin
// 使用reified实现类型安全的JSON反序列化
class JsonParser {
// TODO: 实现inline + reified函数
// inline fun <reified T> parse(json: String): T { ... }
}
// 测试用例
data class User(val name: String, val age: Int)
val json = """{"name": "Alice", "age": 30}"""
val user: User = parser.parse<User>(json) // 应该自动推断类型
总结
Kotlin的泛型系统在Java的基础上做了重要改进,提供了更强大和灵活的类型安全保障。
核心要点回顾
-
型变(Variance)
- out(协变) - 生产者,保持子类型关系,只能输出
- in(逆变) - 消费者,反转子类型关系,只能输入
- 不变 - 默认行为,既可读又可写,无子类型关系
-
使用处型变
- 类型投影:
Array<out Any>,Array<in String> - 星投影:
List<*>≈List<out Any?>
- 类型投影:
-
具体化类型参数(reified)
- 配合
inline函数使用 - 保留运行时类型信息
- 实现类型安全的类型检查和转换
- 配合
-
泛型边界
- 单个上界:
<T : Number> - 多个上界:
where T : A, T : B - 递归边界:
<T : Comparable<T>>
- 单个上界:
最佳实践
- ✅ 遵循PECS原则:Producer用out,Consumer用in
- ✅ 优先使用声明处型变:在类定义时就指定out/in
- ✅ 谨慎使用reified:只在确实需要运行时类型信息时使用
- ✅ 明确泛型边界:使用上界约束类型,提高代码可读性
- ✅ 避免过度泛型:不是所有代码都需要泛型化
- ✅ 利用标准库:Kotlin标准库提供了丰富的泛型工具函数
泛型的设计哲学
Kotlin泛型体现了三个核心理念:
- 类型安全优先 - 在编译期尽可能捕获类型错误
- 灵活性与安全性平衡 - 通过型变提供灵活性,同时保证安全
- 实用主义 - reified等特性解决实际问题,而不拘泥于理论纯粹
正如文章开头的故事,理解泛型的这些高级特性,不仅能让我们写出更安全、更灵活的代码,更重要的是能帮助我们理解Kotlin类型系统的设计哲学。
相关资料
系列文章导航:
- 👉 上一篇: 类型系统深度解析:从空安全到智能类型推断的设计哲学
如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!
也欢迎访问我的个人主页发现更多宝藏资源