这里先定义两个类:
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=>B
和A=>C
两个函数类型;如果C extends B
则A=>C
是A=>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>