TL;DR:
-
您的Pets 类可以产生类型为A 的值,方法是返回成员变量pet,因此Pet[VeryGeneral] 不能是Pet[VerySpecial] 的子类型,因为当它产生VeryGeneral 的东西,它不能保证它也是 VerySpecial 的一个实例。因此,它不可能是逆变的。
-
您的Pets 类可以使用类型为A 的值,方法是将它们作为参数传递给add。因此Pet[VerySpecial] 不能是宠物Pet[VeryGeneral] 的子类型,因为它会阻塞任何不是VerySpecial 的输入。因此,您的类不能是协变的。
唯一剩下的可能性是:Pets 在A 中必须是不变的。
###插图:协方差与逆变:
我将利用这个机会展示一个改进的和显着的更多
this comic 的严格版本。它是协方差和逆变的说明
具有子类型和声明站点差异注释的编程语言的概念
(显然,即使是 Java 人也觉得它很有启发性,
尽管问题是关于使用地点的差异)。
首先,插图:
现在使用可编译的 Scala 代码进行更详细的描述。
###逆变的解释(图1左侧)
考虑以下能源的层次结构,从非常一般到非常具体:
class EnergySource
class Vegetables extends EnergySource
class Bamboo extends Vegetables
现在考虑一个具有单个 consume(a: A) 方法的特征 Consumer[-A]:
trait Consumer[-A] {
def consume(a: A): Unit
}
让我们实现这个特性的几个例子:
object Fire extends Consumer[EnergySource] {
def consume(a: EnergySource): Unit = a match {
case b: Bamboo => println("That's bamboo! Burn, bamboo!")
case v: Vegetables => println("Water evaporates, vegetable burns.")
case c: EnergySource => println("A generic energy source. It burns.")
}
}
object GeneralistHerbivore extends Consumer[Vegetables] {
def consume(a: Vegetables): Unit = a match {
case b: Bamboo => println("Fresh bamboo shoots, delicious!")
case v: Vegetables => println("Some vegetables, nice.")
}
}
object Panda extends Consumer[Bamboo] {
def consume(b: Bamboo): Unit = println("Bamboo! I eat nothing else!")
}
现在,为什么 Consumer 必须在 A 中是逆变的?让我们尝试实例化
几种不同的能源,然后将它们提供给不同的消费者:
val oilBarrel = new EnergySource
val mixedVegetables = new Vegetables
val bamboo = new Bamboo
Fire.consume(bamboo) // ok
Fire.consume(mixedVegetables) // ok
Fire.consume(oilBarrel) // ok
GeneralistHerbivore.consume(bamboo) // ok
GeneralistHerbivore.consume(mixedVegetables) // ok
// GeneralistHerbivore.consume(oilBarrel) // No! Won't compile
Panda.consume(bamboo) // ok
// Panda.consume(mixedVegetables) // No! Might contain sth Panda is allergic to
// Panda.consume(oilBarrel) // No! Pandas obviously cannot eat crude oil
结果是:Fire 可以消费 GeneralistHerbivore 可以消费的所有东西,
反过来GeneralistHerbivore 可以吃掉Panda 可以吃的所有东西。
因此,只要我们只关心消耗能源的能力,
Consumer[EnergySource] 可以在需要 Consumer[Vegetables] 的地方替换,
和
Consumer[Vegetables] 可以在需要 Consumer[Bamboo] 的地方替换。
因此,Consumer[EnergySource] <: Consumer[Vegetables] 和
Consumer[Vegetables] <: Consumer[Bamboo],虽然之间的关系
类型参数正好相反:
type >:>[B, A] = A <:< B
implicitly: EnergySource >:> Vegetables
implicitly: EnergySource >:> Bamboo
implicitly: Vegetables >:> Bamboo
implicitly: Consumer[EnergySource] <:< Consumer[Vegetables]
implicitly: Consumer[EnergySource] <:< Consumer[Bamboo]
implicitly: Consumer[Vegetables] <:< Consumer[Bamboo]
###协方差解释(图1右侧)
定义产品的层次结构:
class Entertainment
class Music extends Entertainment
class Metal extends Music // yes, it does, seriously^^
定义一个可以产生A类型值的特征:
trait Producer[+A] {
def get: A
}
定义不同专业化水平的各种“来源”/“生产者”:
object BrowseYoutube extends Producer[Entertainment] {
def get: Entertainment = List(
new Entertainment { override def toString = "Lolcats" },
new Entertainment { override def toString = "Juggling Clowns" },
new Music { override def toString = "Rick Astley" }
)((System.currentTimeMillis % 3).toInt)
}
object RandomMusician extends Producer[Music] {
def get: Music = List(
new Music { override def toString = "...plays Mozart's Piano Sonata no. 11" },
new Music { override def toString = "...plays BBF3 piano cover" }
)((System.currentTimeMillis % 2).toInt)
}
object MetalBandMember extends Producer[Metal] {
def get = new Metal { override def toString = "I" }
}
BrowseYoutube 是Entertainment 的最通用来源:它可以为您提供
基本上任何类型的娱乐:猫视频,杂耍小丑,或(意外)
一些音乐。
Entertainment 的通用来源由图 1 中的原型小丑表示。
RandomMusician 已经更专业了,至少我们知道这个对象
制作音乐(尽管对任何特定流派没有限制)。
最后,MetalBandMember 非常专业:get 方法保证返回
只有非常具体的 Metal 音乐。
让我们尝试从这三个对象中获取各种Entertainment:
val entertainment1: Entertainment = BrowseYoutube.get // ok
val entertainment2: Entertainment = RandomMusician.get // ok
val entertainment3: Entertainment = MetalBandMember.get // ok
// val music1: Music = BrowseYoutube.get // No: could be cat videos!
val music2: Music = RandomMusician.get // ok
val music3: Music = MetalBandMember.get // ok
// val metal1: Metal = BrowseYoutube.get // No, probably not even music
// val metal2: Metal = RandomMusician.get // No, could be Mozart, could be Rick Astley
val metal3: Metal = MetalBandMember.get // ok, because we get it from the specialist
我们看到所有三个Producer[Entertainment]、Producer[Music] 和Producer[Metal] 都可以产生某种Entertainment。
我们看到只有Producer[Music] 和Producer[Metal] 保证产生Music。
最后,我们看到只有极其专业的Producer[Metal] 才能保证
产生Metal,仅此而已。因此,Producer[Music] 和 Producer[Metal] 可以替换
对于Producer[Entertainment]。 Producer[Metal] 可以替换为Producer[Music]。
一般来说,生产者
可以用一种更具体的产品代替不那么专业的生产商:
implicitly: Metal <:< Music
implicitly: Metal <:< Entertainment
implicitly: Music <:< Entertainment
implicitly: Producer[Metal] <:< Producer[Music]
implicitly: Producer[Metal] <:< Producer[Entertainment]
implicitly: Producer[Music] <:< Producer[Entertainment]
产品之间的子类型关系与产品之间的子类型关系相同
产品的生产者。这就是协方差的意思。
相关链接
-
关于 Java 8 中 ? extends A 和 ? super B 的类似讨论:
Java 8 Comparator comparing() static function
-
经典“在我自己的 Either 实现中,flatMap 的正确类型参数是什么”问题:Type L appears in contravariant position in Either[L, R]