Scala中协变(+)、逆变(-)、上界(<:)、下界(>:)简单介绍

对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合List[A]的子类型,那么就称为covariance(协变) 

如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)。

如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariance(不可变的)  

而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如: 

tarit Person[+T]{} --协变,在这种情况下,假设S类型是A类型的子类,则Person[S]类型可泛化成Person[T]的子类,也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。 

tarit Person[-T]{}  --逆变,这种情况下,当类型S是类型A的子类型,则Person[A]反过来可以认为是Person[S]的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变

Scala的协变和逆变

class Super
class Sub extends Super
class Temp[+A](title: String)//支持协变


object app_main extends App {
  val t: Temp[Super] = new Temp[Sub]("hello world")
  print(t.toString)
}

但要注意的是,variance(可变类型)并不会被继承,父类声明为variance(可变类型),子类如果想要保持,仍需要声明成可变类型,如下代码:

scala> trait A[+T]
defined trait A
scala> class C[T] extends A[T]
defined class C
scala> class X; class Y extends X;
defined class X
defined class Y
scala>
val c:C[X] = new C[Y] //不会继承父类的可变类型
<console>:11: error: type mismatch;
 found   : C[Y]
 required: C[X]
Note: Y <: X, but class C is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
       val c:C[X] = new C[Y]

代码修改如下

scala> class C[+T] extends A[T]
defined class C
scala> val c:C[X] = new C[Y]
c: C[X] = C@7241a47d

Scala逆变和协变的实例分析

在Scala(以及其他许多编程语言)中,函数也是对象,可以使用、定义其他对象的地方,也可以使用、定义函数。Scala中的函数,具有apply方法的类的实例,就可以当做函数来使用。其中apply接受的参数就是函数的参数,而apply的返回值就是函数的返回值。

首先给出一个接受一个参数的函数的泛型定义

trait Function1[-T, +U] {
  def apply(x: T): U
}

这种函数接受一个参数,参数类型为泛型类型T,返回类型为泛型类型U。和其他支持泛型的语言一样,实际定义函数时T和U的类型会被确定下来,不过需要注意的是,这边的T之前有一个“-”,而U之前有一个“+”。

在这里引入关于这个符号的说明,在声明Scala的泛型类型时,“+”表示协变,而“-”表示逆变 

  1. C[+T]:如果A是B的子类,那么C[A]是C[B]的子类。>>>>>协变 

  2. C[-T]:如果A是B的子类,那么C[B]是C[A]的子类。>>>>>逆变 

  3. C[T]:无论A和B是什么关系,C[A]和C[B]没有从属关系。

根据Liskov替换原则, 如果A是B的子类,那么能适用于B的所有操作,都适用于A。 让我们看看这边Function1的定义,是否满足这样的条件。假设Bird是Animal的子类,那么看看下面两个函数之间是什么关系:

def f1(x: Bird): Animal // instance of Function1[Bird, Animal]

def f2(x: Animal): Bird // instance of Function1[Animal, Bird]


在这里f2的类型是f1的类型的子类。为什么?

我们先看一下参数类型,根据Liskov替换原则,f1能够接受的参数,f2也能接受 。在这里f1接受的Bird类型,f2显然可以接受,因为Bird对象可以被当做其父类Animal的对象来使用。

再看返回类型,f1的返回值可以被当做Animal的实例使用,f2的返回值可以被当做Bird的实例使用,当然也可以被当做Animal的实例使用。

所以我们说,函数的参数类型是逆变的,而函数的返回类型是协变的。

函数的参数类型是逆变的,而函数的返回类型是协变的 

那么我们在定义Scala类的时候,是不是可以随便指定泛型类型为协变或者逆变呢?答案是否定的。通过上面的例子可以看出,如果将Function1的参数类型定义为协变,或者返回类型定义为逆变,都会违反Liskov替换原则,因此,Scala规定,协变类型只能作为方法的返回类型,而逆变类型只能作为方法的参数类型。类比函数的行为,结合Liskov替换原则,就能发现这样的规定是非常合理的。

Scala上界(<:)和下界(>:)

1) U >: T

这是类型下界的定义,也就是U必须是类型T的父类(或本身,自己也可以认为是自己的父类)。

2) S <: T

这是类型上界的定义,也就是S必须是类型T的子类(或本身,自己也可以认为是自己的子类)。



参考博客地址:http://my.oschina.net/xinxingegeya/blog/486671


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值