Fork me on GitHub
Hike News
Hike News

Kotlin 泛型进阶:in, out 与 reified

1. 泛型基础

泛型本质上是参数化类型。它让我们能够编写可以处理不同类型数据的代码,同时保持类型安全。

1
2
3
class Box<T>(t: T) {
var value = t
}

2. 型变 (Variance):out 与 in

这是 Kotlin 泛型中最难理解的部分。它的核心是为了解决:**List<String> 是否是 List<Any> 的子类?**

2.1 协变 (Covariance):out

  • 定义:如果 StringAny 的子类,那么 Producer<String> 也是 Producer<Any> 的子类。
  • 限制:只能从对象中读取(Produce),不能写入(Consume)。
  • 关键字out
1
2
3
4
5
// 声明处型变
interface Producer<out T> {
fun produce(): T // OK
// fun consume(item: T) // 编译错误:Type parameter T is declared as 'out'
}

2.2 逆变 (Contravariance):in

  • 定义:如果 StringAny 的子类,那么 Consumer<Any> 反而是 Consumer<String> 的子类。
  • 限制:只能向对象中写入(Consume),不能读取(Produce)。
  • 关键字in
1
2
3
4
interface Consumer<in T> {
fun consume(item: T) // OK
// fun produce(): T // 编译错误:Type parameter T is declared as 'in'
}

2.3 为什么需要型变?

Java 的泛型是不型变的(Invariant)。在 Java 中,List<Object> list = new ArrayList<String>(); 是不允许的。Kotlin 通过 outin 优雅地解决了生产与消费场景下的类型兼容问题。


3. 类型擦除与实化类型参数 (reified)

3.1 类型擦除

在运行时,泛型信息会被擦除。这意味着你不能在运行时直接判断一个对象的泛型类型:

1
2
// 错误示例
fun <T> isType(value: Any) = value is T // 编译错误:Cannot check for instance of erased type: T

3.2 reified 关键字

Kotlin 引入了 inline 函数配合 reified 关键字,使得我们可以在运行时访问泛型类型。

1
2
3
4
5
6
7
8
9
inline fun <reified T> isType(value: Any): Boolean {
return value is T // 此时是合法的!
}

// 实际应用场景:启动 Activity
inline fun <reified T : Activity> Context.startActivity() {
val intent = Intent(this, T::class.java)
startActivity(intent)
}

4. 泛型约束 (Upper Bounds)

我们可以通过 : 来限制泛型必须是某个类的子类。

1
2
3
4
5
6
7
8
9
// T 必须是 Number 或其子类
fun <T : Number> sum(a: T, b: T) {
// ...
}

// 多个约束使用 where 关键字
fun <T> copyData(source: T) where T : CharSequence, T : Appendable {
// ...
}

5. 星投影 (Star-projections) *

当你不知道或者不关心泛型的具体类型时,可以使用 *

  • List<*> 代表“我不知道这里面是什么类型,但我知道它一定是 Any? 的子类”。
  • 它是只读的(类似 out Any?)。

总结

  • **out (协变)**:生产者,子类到父类,只读。
  • **in (逆变)**:消费者,父类到子类,只写。
  • **reified**:打破类型擦除,运行时获取类型信息。
  • **:**:类型约束。
,