引言
我们是不是常常在 Kotlin 的代码中看到一些奇怪的符号,比如 <out T>
或者 <T>
?如果我们对这些泛型(Generics)符号还不太了解,没关系!今天我们就来聊一聊它们的区别,以及如何在实际开发中正确使用它们。😊
泛型:它是什么?为什么要用?📚
首先,让我们简单了解一下**泛型(Generics)**是什么。泛型就是一种设计模式,它让我们的代码更加通用化,能够适用于多种数据类型,而不是被限制在某一种类型上。
举个例子:
fun printItem(item: Any) {
println(item)
}
这个 printItem
函数看起来不错,它接受 Any
类型的参数,意味着任何类型的对象都能传进来。😃
但是,Kotlin 的泛型可以让我们做得更好!假设我们希望创建一个可以存储和获取不同类型数据的容器类,我们可以使用泛型来定义它:
class Container<T>(var item: T) {
fun getItem(): T {
return item
}
}
这里的 <T>
就是泛型标记,表示我们将使用一个类型 T
,并且这个类型是由使用者来指定的。
那么,<T>
和 <out T>
有什么区别呢?🧐
1. <T>
:协变与逆变
在 Kotlin 中,泛型类型参数是不变的(Invariant)。这意味着对于一个类 Container<T>
,如果 A
是 B
的子类型(A : B
),那么 Container<A>
并不是 Container<B>
的子类型。比如:
val stringList: List<String> = listOf("Hello")
val anyList: List<Any> = stringList // 错误:类型不匹配
即使 String
是 Any
的子类型,List<String>
也不是 List<Any>
的子类型。这种类型的不兼容性在一些情况下会带来麻烦。
2. out
:协变(Covariance)
为了让泛型类型在子类型关系中表现得更灵活,Kotlin 引入了协变和逆变的概念。
- 协变:用
out
关键字表示,意味着泛型类型可以从一种类型安全地转换为另一种类型。 - 协变适用于只读的情况,也就是说,我们只从中获取数据,不会修改其中的数据。
举个例子:
interface Source<out T> {
fun nextT(): T
}
这里的 out T
表示 Source
是协变的,这意味着如果 Cat
是 Animal
的子类型,那么 Source<Cat>
也是 Source<Animal>
的子类型。
为什么这样?因为 Source
只提供 T
类型的数据,而不修改它。这保证了类型安全。
使用 out
的场景
当我们在设计类或接口时,如果我们希望该类的泛型类型参数仅用于返回(输出)数据,而不会用于接收(输入)数据时,我们应该使用 out
关键字。🌟
fun demo(source: Source<Animal>) {
val catSource: Source<Cat> = object : Source<Cat> {
override fun nextT(): Cat {
return Cat()
}
}
val animalSource: Source<Animal> = catSource // 协变,安全转换
}
3. in
:逆变(Contravariance)
与 out
相反,in
用于表示逆变(Contravariance)。in
关键字表示泛型类型参数只能用于接收(输入)数据,而不能用于返回(输出)数据。💡
逆变的应用
假设我们有一个消费者类,它只消耗数据而不产生数据:
interface Consumer<in T> {
fun consume(item: T)
}
在这种情况下,如果 Animal
是 Cat
的父类型,那么 Consumer<Animal>
是 Consumer<Cat>
的子类型。逆变可以安全地传递更广泛类型的对象。
4. 不使用 in
或 out
:不变(Invariant)
当我们既需要输入又需要输出数据时,就不应该使用 in
或 out
。这样的泛型类型参数称为不变(Invariant)。
class Container<T>(var item: T) {
fun getItem(): T = item
fun setItem(item: T) {
this.item = item
}
}
这里的 Container<T>
同时用于输入和输出 T
类型的数据,因此不能使用 in
或 out
,它是不变的。
总结 📝
<T>
:默认情况下是不变的,即Container<A>
不是Container<B>
的子类型。<out T>
:表示泛型参数是协变的,只能作为返回值(输出)使用。这种情况适用于只读取数据的场景。<in T>
:表示泛型参数是逆变的,只能作为参数(输入)使用。这种情况适用于只传递数据的场景。
感谢阅读!