返回

Kotlin 泛型的逆变和协变:深入理解

Android

逆变和协变:解锁 Kotlin 中泛型的力量

导言

泛型是 Kotlin 中一项变革性的功能,它赋予您创建可与多种数据类型无缝协作的代码的能力。逆变和协变是泛型中至关重要的概念,它们为编写更灵活、更强健的代码铺平了道路。

逆变:子类化力量

逆变的本质在于允许您用子类化对象替换泛型中的对象。换句话说,您可以毫无顾忌地将子类对象传递给父类类型的参数,反之亦然。

示例:

open class Animal

class Cat : Animal()

fun printAnimal(animal: Animal) {
    println(animal)
}

fun main() {
    val cat = Cat()
    printAnimal(cat) // 编译通过
}

在上面示例中,printAnimal() 函数接收一个 Animal 类型参数。凭借逆变的魔力,我们可以传入一个 Cat 对象(cat),因为 CatAnimal 的子类。

协变:超类化灵活性

协变与逆变相辅相成,它允许您用超类化对象替换泛型中的对象。这意味着您可以轻松地将超类对象分配给子类类型参数,反之亦然。

示例:

interface Drawable {
    fun draw()
}

class Circle : Drawable {
    override fun draw() {
        println("绘制圆形")
    }
}

class Square : Drawable {
    override fun draw() {
        println("绘制正方形")
    }
}

fun drawAll(drawables: List<Drawable>) {
    for (drawable in drawables) {
        drawable.draw()
    }
}

fun main() {
    val circle = Circle()
    val square = Square()
    val drawables = listOf(circle, square)
    drawAll(drawables) // 编译通过
}

在此示例中,drawAll() 函数接收 List<Drawable> 类型参数。得益于协变,我们可以传入 List<Circle> 列表(drawables),因为 CircleDrawable 的超类。

实际应用:灵活性和通用性的基石

逆变和协变在实际应用中大放异彩,为编写更灵活、更通用的代码奠定了基础。以下是一些常见的应用场景:

  • 数据结构: 逆变可以实现支持多种数据类型的栈或队列。
  • 回调: 协变可用于创建可以处理多种对象类型的回调接口。
  • 适配器: 逆变和协变的结合可以创建适配器类,将不同类型的数据适配到现有的接口。

结论:赋能 Kotlin 代码

逆变和协变是 Kotlin 泛型库中不可或缺的元素,它们为编写更灵活、更强大的代码提供了无穷的可能。通过理解并善用这些概念,您可以充分利用 Kotlin 泛型的全部潜力。

常见问题解答

  1. 逆变和协变之间有什么区别?

    • 逆变允许子类化对象替换父类类型对象,而协变允许超类化对象替换子类类型对象。
  2. 为什么逆变和协变很重要?

    • 它们提高了代码的灵活性和通用性,使您可以编写更易于维护和重用的代码。
  3. 可以在哪些实际场景中应用逆变和协变?

    • 实现支持多种数据类型的栈、创建可以处理不同类型对象的回调,以及构建适配不同类型数据的适配器。
  4. 逆变和协变有什么局限性?

    • 它们只适用于协变或逆变接口和类,并且必须明确指定协变或逆变类型参数。
  5. 如何判断类型参数是协变还是逆变?

    • 协变类型参数用 out 修饰,而逆变类型参数用 in 修饰。