在这里插入图片描述

引言

我们是不是常常在 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>,如果 AB 的子类型(A : B),那么 Container<A> 并不是 Container<B> 的子类型。比如:

val stringList: List<String> = listOf("Hello")
val anyList: List<Any> = stringList // 错误:类型不匹配

即使 StringAny 的子类型,List<String> 也不是 List<Any> 的子类型。这种类型的不兼容性在一些情况下会带来麻烦。

2. out:协变(Covariance)

为了让泛型类型在子类型关系中表现得更灵活,Kotlin 引入了协变和逆变的概念。

  • 协变:用 out 关键字表示,意味着泛型类型可以从一种类型安全地转换为另一种类型。
  • 协变适用于只读的情况,也就是说,我们只从中获取数据,不会修改其中的数据。

举个例子:

interface Source<out T> {
    fun nextT(): T
}

这里的 out T 表示 Source 是协变的,这意味着如果 CatAnimal 的子类型,那么 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)
}

在这种情况下,如果 AnimalCat 的父类型,那么 Consumer<Animal>Consumer<Cat> 的子类型。逆变可以安全地传递更广泛类型的对象。

4. 不使用 inout:不变(Invariant)

当我们既需要输入又需要输出数据时,就不应该使用 inout。这样的泛型类型参数称为不变(Invariant)。

class Container<T>(var item: T) {
    fun getItem(): T = item
    fun setItem(item: T) {
        this.item = item
    }
}

这里的 Container<T> 同时用于输入和输出 T 类型的数据,因此不能使用 inout,它是不变的。

总结

  • <T>:默认情况下是不变的,即 Container<A> 不是 Container<B> 的子类型。
  • <out T>:表示泛型参数是协变的,只能作为返回值(输出)使用。这种情况适用于只读取数据的场景。
  • <in T>:表示泛型参数是逆变的,只能作为参数(输入)使用。这种情况适用于只传递数据的场景。

感谢阅读!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部