美文网首页
Scala中的协变与逆变

Scala中的协变与逆变

作者: AlienPaul | 来源:发表于2018-05-29 21:43 被阅读0次

什么是协变和逆变

给出Student和Person两个类,其中Student为Person的子类,对于协变来说List[Student]是List[Person]的子类。相反,对于逆变来说List[Person]为List[Student]的子类。

协变和逆变的用途

一句话描述为:生产者使用协变,消费者使用逆变。

下面我们来解释为何这么使用。

生产者我们使用的场景为食品生产商。先定义如下两种食品:

class Food

class Meat extends Food

加入我们定义的食品生产商如下,采用类型不变:

class Producer[T] {
    def produce: T = new T
}

我们只能这么调用:

val p1 = new Producer[Meat]
val f1 = p1.produce // f1为Meat类型
val p2: Producer[Food] = p1
val f2 = p2.produce // 看似f2为Food类型,但是Producer中的类型T是不可变的,这两行会编译错误

现实中,肉类的生产商是食品生产商。即肉类生产商是食品生产商的子类。
为了满足要求,我们将Producer定义为协变:

class Producer[+T] {
    def produce: T = new T
}

val p1 = new Producer[Meat]
val f1 = p1.produce // f1为Meat类型
val p2: Producer[Food] = p1 // 此行合法。因为根据协变的定义,Producer[Food]是Producer[Meat]的父类,根据多态,可以使用父类引用指向子类对象
val f2 = p2.produce // f2实际为Meat类型,但是被当做Food类型看待

对于逆变来说可能不是这么容易理解,直接上消费者的例子

class Animal[T]  { //这里先不写协变或者逆变
    def eat(t: T) = println("ate") // 该动物把T类型食物吃掉
}

下面有两种动物:

val pig = new Animal[Food] //猪猪是杂食动物,吃什么都行
pig.eat(food)
val tiger = new Animal[Meat] // 老虎只吃肉
tiger.eat(meat)

对于这两种动物,如果我们只有肉的情况下,可以同时养活猪和老虎,但是如果只有普通食物,是没有办法养活老虎的。如果普通食物短缺我们可以把猪当做食肉动物来养,代码如下:

val a: Animal[Meat] = pig
a.eat(meat)

val b: Animal[Food] = tiger // 不合适,老虎不吃普通食物

根据多态,父类引用可以指向子类对象,即Animal[Meat]可以指向Animal[Food]类型。所以说Animal[Meat]Animal[Food]的父类。根据定义,Animal的类型T为逆变。
经过分析,Animal类的定义需要完善为:

class Animal[-T]  { //T为逆变
    def eat(t: T) = println("ate")
}

一个看似自相矛盾的问题

假如我们自己实现一个List:

trait List[T] {
    def add(t: T)
    def get(i: Int): T
}

对于add方法来说是消费者,T应该为逆变,可是对于get方法来说是生产者,T该为协变才对。看起来自相矛盾。
add方法的t位于逆变的位置,因此这里可以使用E >: T,传入一个T的父类E。将T转化为逆变类型。为什么说这样可以转换成逆变类型呢?根据李氏替换原则,任何使用父类的地方都可以使用其子类替换。这里可以使用T的超类E进行替换,所以说这里为逆变点。

该自相矛盾的问题便可以迎刃而解:

trait List[+T] {
    def add[E >: T](e: E)
    def get(i: Int): T
}

相关文章

  • Scala教程之:深入理解协变和逆变

    在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型;使用-表示逆变类型;非转化类型...

  • Scala 通俗易懂 ---- 协变、逆变、不变

    协变、逆变、不变 Scala 语言中协变、逆变、不变是指拥有泛型的类型,在声明和赋值时的对应关系 协变:声明时泛型...

  • Typescript 中的协变和逆变

    Typescript的协变和逆变和C# Scala中的类似,但是Typescript的会自动算出来接口属于协变还是...

  • Scala 类型系统(1)

    协变逆变引入原因 协变和逆变主要是用来解决参数化类型的泛化问题。我的理解是解决Scala高阶函数参数引入。 定义协...

  • Scala中的协变与逆变

    什么是协变和逆变 给出Student和Person两个类,其中Student为Person的子类,对于协变来说Li...

  • Scala中的协变与逆变

    协变与逆变的概念 对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合...

  • JAVA泛型与类型安全

    1. 基础泛型 2. 协变与逆变与不变 协变 简单来说即: Java中的数组是协变的 逆变与协变相对,逆转了类型关...

  • Kotlin 泛型协变与逆变的理解

    协变与逆变定义 逆变与协变用来描述类型转换后的继承关系 协变:如果 A 是 B 的子类型,并且Generic 也...

  • 泛型编程中的型变

    在泛型编程中,经常会提到型变。型变分为两种:协变与逆变。协变covariant表示与泛型参数T的变化相同,而逆变c...

  • Java协变和逆变

    泛型的协变与逆变 协变与逆变用来描述类型转换(type transformation)后的继承关系,其定义如下:如...

网友评论

      本文标题:Scala中的协变与逆变

      本文链接:https://www.haomeiwen.com/subject/gdtzjftx.html