一、泛型的基本概念
泛型是一种编程机制,允许在定义类、接口或方法时使用类型参数。这样,在使用时可以指定具体的类型,从而实现代码的复用并提高类型安全性,避免了显式的类型转换和潜在的运行时错误。例如,List<String> 就明确了集合内只能存放字符串。
二、关键字out、in的使用
//out的使用
interface IContainer<out R>{
fun get():R
}
//in的使用
interface IContainer<in V>{
fun set(data:V)
}
三、泛型的进阶概念:型变 (Variance)、out (协变 - Covariant)、in (逆变 - Contravariant)
- 不变 (Invariant) :泛型的默认行为,即使
A是B的子类,Container<A>与Container<B>之间也没有任何派生关系。 - 型变 (## Variance) :是指泛型类型在基础类型发生变化时,其派生泛型类型如何跟随变化的性质。
- 协变 (Covariant) :如果
A是B的子类,那么Container<A>被视为Container<B>的子类,则称之为协变。在 Kotlin 中使用out关键字,表示该容器只能作为“生产者”输出数据。 - 逆变 (Contravariant) :如果
A是B的子类,那么Container<B>被视为Container<A>的子类,则称之为逆变。在 Kotlin 中使用in关键字,表示该容器只能作为“消费者”输入数据。
四、为什么需要out、in
举例说明:
interface Fruit // 定义“水果”类型
class Apple : Fruit // 定义“苹果”类型
class Banana : Fruit // 定义“香蕉”类型
class Container<T> // 定义容器
虽然 Apple 和 Banana 是Fruit 的子类,但是Container与Container、Container 之间没有任何关系,也不能相互赋值。
var fruitContainer: Container<Fruit> = Container<Fruit>()
var appleContainer: Container<Apple> = Container<Apple>()
val BananaContainer: Container<Banana> = Container<Banana>()
// 下面的写法都是错的,不能编译
fruitContainer = appleContainer
fruitContainer = BananaContainer
appleContainer = BananaContainer
当限制条件满足时,这种类型转换在逻辑上是符合直觉的,如下代码所示:
open class Fruit
class Apple : Fruit()
class Banana : Fruit()
abstract class Container<T> {
abstract fun get(): T
}
var fruitContainer: Container<Fruit> = object : Container<Fruit>() {
override fun get(): Fruit {
return Fruit()
}
}
var appleContainer: Container<Apple> = object : Container<Apple>() {
override fun get(): Apple {
return Apple()
}
}
fruitContainer 预期的结果类型是 Fruit,而 appleContainer 只能提供 Apple,因为Apple 是 Fruit 的子类,从抽象逻辑上来看 fruitContainer = appleContainer 是可以的。但是编译器无法理解,因此需要显式地告诉编译器它们之间存在的关系。
使用 out 关键字定义协变关系(限定输出类型):
abstract class Container<out T> {
abstract fun get(): T
}
var appleContainer: Container<Apple> = object : Container<Apple>() {
override fun get(): Apple {
return Apple()
}
}
fruitContainer = appleContainer // 可以通过编译
使用 in 关键字定义逆变关系(限定输入的类型):
abstract class Container<in T> {
abstract fun set(data: T)
}
var fruitContainer: Container<Fruit> = object : Container<Fruit>() {
override fun set(data: Fruit) {
TODO("Not yet implemented")
}
}
// fruitContainer 是可以赋值给 appleContainer 的,可以通过编译
var appleContainer: Container<Apple> = fruitContainer
appleContainer.set(Apple())
五、核心意义
引入 out 和 in 关键字是为了打破泛型默认的“不变性”,在逻辑合理且类型安全的前提下,允许更灵活的类型转换与赋值。