Kotlin 泛型中的 in 和 out

简评:在 Kotlin 中使用泛型你会注意到其中引入了 in 和 out,对于不熟悉的开发者来说可能有点难以理解。从形式上讲,这是一种定义逆变和协变的方式,这篇文章就来讲讲怎么来理解和记住它们。

in & out 怎么记?

Out (协变)

如果你的类是将泛型作为内部方法的返回,那么可以用 out:

interface Production<out T> {
    fun produce(): T
}

可以称其为 production class/interface,因为其主要是产生(produce)指定泛型对象。因此,可以这样来记:produce = output = out

In(逆变)

如果你的类是将泛型对象作为函数的参数,那么可以用 in:

interface Consumer<in T> {
    fun consume(item: T)
}

可以称其为 consumer class/interface,因为其主要是消费指定泛型对象。因此,可以这样来记:consume = input = in。

Invariant(不变)

如果既将泛型作为函数参数,又将泛型作为函数的输出,那就既不用 in 或 out。

interface ProductionConsumer<T> {
    fun produce(): T
    fun consume(item: T)
}

举个例子

假设我们有一个汉堡(burger)对象,它是一种快餐,当然更是一种食物。

open class Food
open class FastFood : Food() 
class Burger : FastFood()

1. 汉堡提供者

根据上面定义的类和接口来设计提供 food, fastfoodburger 的类:

class FoodStore : Production<Food> {
    override fun produce(): Food {
        println("Produce food")
        return Food()
    }
}

class FastFoodStore : Production<FastFood> {
    override fun produce(): FastFood {
        println("Produce food")
        return FastFood()
    }
}

class InOutBurger : Production<Burger> {
    override fun produce(): Burger {
        println("Produce burger")
        return Burger()
    }
}

现在,我们可以这样赋值:

val production1 : Production<Food> = FoodStore()
val production2 : Production<Food> = FastFoodStore()
val production3 : Production<Food> = InOutBurger()

很显然,汉堡商店属于是快餐商店,当然也属于食品商店。

因此,对于 out 泛型,我们能够将使用子类泛型的对象赋值给使用父类泛型的对象。

而如果像下面这样反过来使用子类 - Burger 泛型,就会出现错误,因为快餐(fastfood)和食品(food)商店不仅仅提供汉堡(burger)。

val production1 : Production<Burger> = FoodStore()  // Error
val production2 : Production<Burger> = FastFoodStore()  // Error
val production3 : Production<Burger> = InOutBurger()

2. 汉堡消费者

再让我们根据上面的类和接口来定义汉堡消费者类:

class Everybody : Consumer<Food> {
    override fun consume(item: Food) {
        println("Eat food")
    }
}

class ModernPeople : Consumer<FastFood> {
    override fun consume(item: FastFood) {
        println("Eat fast food")
    }
}

class American : Consumer<Burger> {
    override fun consume(item: Burger) {
        println("Eat burger")
    }
}

现在,我们能够将 Everybody, ModernPeople 和 American 都指定给汉堡消费者(Consumer<Burger>):

val consumer1 : Consumer<Burger> = Everybody()
val consumer2 : Consumer<Burger> = ModernPeople()
val consumer3 : Consumer<Burger> = American()

很显然这里美国的汉堡的消费者既是现代人,更是人类。

因此,对于 in 泛型,我们可以将使用父类泛型的对象赋值给使用子类泛型的对象。

同样,如果这里反过来使用父类 - Food 泛型,就会报错:

val consumer1 : Consumer<Food> = Everybody()
val consumer2 : Consumer<Food> = ModernPeople()  // Error
val consumer3 : Consumer<Food> = American()  // Error

根据以上的内容,我们还可以这样来理解什么时候用 in 和 out:

  • 父类泛型对象可以赋值给子类泛型对象,用 in;
  • 子类泛型对象可以赋值给父类泛型对象,用 out。

英文原文:In and out type variant of Kotlin

推荐阅读更多精彩内容

  • 前言 泛型(Generics)的型变是Java中比较难以理解和使用的部分,“神秘”的通配符,让我看了几遍《Java...
    珞泽珈群阅读 5,095评论 11 45
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 29,007评论 18 399
  • 赵威后是个奇葩老太太,也是一个明白老太太。她在会见娘家人齐国使者的时候,先询问百姓的情况,后询问国君的情况。颇令使...
    二班班阅读 102评论 0 0
  • 怀胎十月,终于等到“卸货”这一刻!But,是不是生完宝宝就一身轻松了呢?当然不是,宝宝出生后,以下这4件事妈妈早做...
    第6通道阅读 94评论 0 0
  • 一丝不挂的枝头 先飞来了三只 又飞来了两只 转眼间 飞走了四只 还有一只 为什么 你还在这里
    行者D也阅读 59评论 0 0