scala的协变与逆变、上界与下界

这里先定义两个类:

class School
class Student extends School

协变

[+T],covariant (or "flexible")in its type parameter T,类似Java中的(? extends T),即可以用T 和T的子类替换T,即类与类型朝着同样的方向型变。如:

class Stack[T]

 []中的T不带符号,则表示Stack与T是不变的,所以Stack[school] 与 Stack[Student] 没有任何的关系,是两个独立的类。

class Stack[+T]

+号意味着Stack与T是协变的,Stack与T朝着同样的方向型变

因为Student类是School类的子类,所以Stack[Student]也是Stack[School]的子类,即:Stack[Student] extends Stack[School]

String是AnyRef的子类,List是支持协变的,所以List[String]也是List[AnyRef]的子类,所以如下示例是可以的:

scala> val s:AnyRef = "abc"
s: AnyRef = abc
## 因为List是支持协变的,所以,如下示例也是可以的:
scala> val objects: List[AnyRef] = List[String]("abc","123")
objects: List[AnyRef] = List(abc, 123)

逆变

[-T],contravariant, 类似(? supersT) 
if T is a subtypeof type S, this would imply that Queue[S] is a subtype of Queue[T]

如果T是类型S的子类,也即意味着Queue[S]是Queue[T]的子类。

class Stack[-T]

  -号意味着Stack与T是逆变的,Stack与T朝着相反的方向型变。

  因为Student类是School类的子类,所以Stack[School]是Stack[Student]的子类,即:Stack[School] extends Stack[Student]

这个比较难于理解, 在什么地方会用到?

 比如下面的OutputChannel,

String是AnyRef的子类, 但是OutputChannel[AnyRef],却是OutputChannel[String]的子类, 怎么理解?

对于OutputChannel[String], 支持的操作就是输出一个string, 同样OutputChannel[AnyRef]也一定可以支持输出一个string,因为它支持输出任意一个AnyRef(它要求的比OutputChannel[String]少) 。

但反过来就不行, OutputChannel[String]只能输出String, 显然不能替换OutputChannel[AnyRef] (参考自http://www.cnblogs.com/fxjwind/p/3480462.html)

上界、下界

scala中,上下界用于泛型类中的方法的参数类型上,能限制应用在该泛型上的类型。

对于最外层层[+T]是协变,则到了方法的类型参数时,该位置发生了翻转,成为了逆变的位置,使用[U >: T],其中T为下界,表示T或T的超类。

class Queue[+T] (private val leading: List[T], private val trailing: List[T]) {
   def append[U >: T](x: U) = new Queue[U](leading, x :: trailing)   //只允许使用T的超类U来替换T
}

同样,对于上界也是:

trait OutputChannel[-T] {
    def write[u <: T] (x: U)   // 只允许使用T的子类来替换T
}

因为Function的泛型里定义了函数入参和出参分别是逆变协变的:

trait Function1[-T1, +R] {…}

所以A=>B这样的函数类型,也可以有继承关系的。我们做个测试,先简单些,只看出参类型的(协变容易理解些)A=>BA=>C两个函数类型;如果C extends BA=>CA=>B的子类型:

scala> class A; class B; class C extends B 

//定义A=>C类型的函数
scala> val t2 = (p:A)=>new C 

//可以把 A=>C类型的函数赋值给 A=>B类型的
scala> val t3:A=>B = t2 

//或直接把t2造型为 A=>B
scala> t2.asInstanceOf[A=>B]

再看看入参类型,这个是逆变,继承关系正好相反。
假设X=>R,Y=>R 如果 Y extends X X=>R  Y=>R 的子类型

scala> class R; class X; class Y extends X  
 
//定义X=>R类型的函数
scala> val f1 = (x:X)=>new R
 
//把X=>R类型的函数赋值给 Y=>R 类型的
scala> val f2:Y=>R = f1
 
//或直接造型
scala> f1.asInstanceOf[Y=>R]

协变和逆变的场景与java泛型的”PECS原则”一致

注:PECS JoshuaBloch在《EffictiveJava》里提出的一个原则。

当参数(容器)是一个生产者(producer)提供元素给你的代码来用(即容器只读),那么容器的泛型应该使用:  Collection<? extends T >

当参数(容器)作为一个消费者(consumer)来消费你提供的数据(即容器可写),那么容器的泛型应该使用:   Collection<? super T >

scala> class A; class B; class C extends B
defined class A
defined class B
defined class C

scala> val t2=(p:C)=> new A
t2: C => A = <function1>

scala> val t3:B=>A = t2   ## 因为B是入参,所以是逆变,所以B=>A是C=>A的子类,所以报错
<console>:12: error: type mismatch;
 found   : C => A
 required: B => A
       val t3:B=>A = t2
                     ^

scala> val t21 = (p:A)=> new C  
t21: A => C = <function1>

scala> val t31:A=>B =t21  ## 因为C是出参,所以是协变,所以A=>B是A=>C的父类,所以正确
t31: A => B = <function1>


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值