您的位置:首页 > 其它

scala的协变逆变,上界下界

2016-11-11 23:05 417 查看
对于一个带类型参数的类型,比如
List[T]
,如果对
A
及其子类型
B
,满足
List[B]
也符合
List[A]
的子类型,那么就称为covariance(协变),如果
List[A]
List[B]
的子类型,即与原来的父子关系正相反,则称为contravariance(逆变)协变:
_____               _____________
|     |             |             |
|  A  |             |  List[ A ]  |
|_____|             |_____________|
^                       ^
|                       |
_____               _____________
|     |             |             |
|  B  |             |  List[ B ]  |
|_____|             |_____________|
逆变:
_____               _____________
|     |             |             |
|  A  |             |  List[ B ]  |
|_____|             |_____________|
^                       ^
|                       |
_____               _____________
|     |             |             |
|  B  |             |  List[ A ]  |
|_____|             |_____________|
如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariant(不可变的)在Java里,泛型类型都是invariant,比如
List<String>
并不是
List<Object>
的子类型。Java并不支持声明点变型(declaration-sitevariance,即在定义一个类型时声明它为可变型,也称definition-site),而scala支持,可以在定义类型时声明(用加号表示为协变,减号表示逆变),如:
trait List[+T] // 在类型定义时(declaration-site)声明为协变
这样会把
List[String]
作为
List[Any]
的子类型。不过Java支持使用点变型(use-site variance),所谓“使用点“,也就是在声明变量时:
List<? extends Object> list = new ArrayList<String>();
scala为了兼容java泛型通配符的形式,引入存在类型(existential type,后边再讲)时,也支持了使用点变型(use-site variance)
scala> val a : List[_ <: Any] = List[String]("A")
a: List[_] = List(A)
要注意variance并不会被继承,父类声明为variance,子类如果想要保持,仍需要声明:
scala> trait A[+T]

scala> class C[T] extends A[T]  // C是invariant的

scala> class X; class Y extends X;

scala> val t: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)
必须也对C声明为协变的才行:
scala> class C[+T] extends A[T]

scala> val t:C[X] = new C[Y]
t: C[X] = C@6a079142
当我们定义一个协变类型List[A+]时,List[Child]可以是List[Parent]的子类型。当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型。回到顶部

Scala的协变

看下面的例子:
    class Animal {}class Bird extends Animal {}class Animal {}class Bird extends Animal {}//协变class Covariant[T](t:T){}val cov = new Covariant[Bird](new Bird)val cov2:Covariant[Animal] = cov
c不能赋值给c2,因为Covariant定义成不变类型。稍微改一下:
    class Animal {}class Bird extends Animal {}class Animal {}class Bird extends Animal {}//协变class Covariant[+T](t:T){}val cov = new Covariant[Bird](new Bird)val cov2:Covariant[Animal] = cov
因为Consumer定义成协变类型的,所以Covariant[Bird]是Covariant[Animal]的子类型,所以它可以被赋值给c2。回到顶部

Scala的逆变

将上面的例子改一下:
    class Animal {}class Bird extends Animal {}class Contravariant[-T](t: T) {}val c: Contravariant[Animal] = new Contravariant[Animal](new Animal)val c2: Contravariant[Bird] = c
这里Consumer[-T]定义成逆变类型,所以Contravariant[Animal]被看作Contravariant[Animal]的子类型,故c可以被赋值给c2。回到顶部

下界lower bounds

如果协变类包含带类型参数的方法时:
    class Animal {}class Bird extends Animal {}class Consumer[+T](t: T) {def use(t: T) = {}}
编译会出错。出错信息为 "Covariant type T occurs in contravariant position in type T of value t"。但是如果返回结果为类型参数则没有问题。
    class Animal {}class Bird extends Animal {}class Consumer[+T](t: T) {def get(): T = {new T}}
为了在方法的参数中使用类型参数,你需要定义下界:
    class Animal {}class Bird extends Animal {}class Consumer[+T](t: T) {def use[U >: T](u : U) = {println(u)}}
这个地方比较复杂, 简单的说就是Scala内部实现是, 把类中的每个可以放类型的地方都做了分类(+, –, 中立), 具体分类规则不说了 对于这里最外层类[+T]是协变, 但是到了方法的类型参数时, 该位置发生了翻转, 成为-逆变的位置, 所以你把T给他, 就会报错说你把一个协变类型放到了一个逆变的位置上所以这里的处理的方法就是, 他要逆变, 就给他个逆变, 使用[U >: T], 其中T为下界, 表示T或T的超类, 这样Scala编译器就不报错了回到顶部

上界upper bounds

看一下逆变类中使用上界的例子:
    class Animal {}class Bird extends Animal {}class Consumer[-T](t: T) {def get[U <: T](): U = {new U}}
可以看到方法的返回值是协变的位置,方法的参数是逆变的位置。因此协变类的类型参数可以用在方法的返回值的类型,在方法的参数类型上必须使用下界绑定 >:。逆变类的类型参数可以用在方法的参数类型上,用做方法的返回值类型时必须使用上界绑定 <:。综合协变,逆变,上界,下界一个综合例子:
    class Animal {}class Bird extends Animal {}class Consumer[-S,+T]() {def m1[U >: T](u: U): T = {new T} //协变,下界def m2[U <: S](s: S): U = {new U} //逆变,上界}class Test extends App {val c:Consumer[Animal,Bird] = new Consumer[Animal,Bird]()val c2:Consumer[Bird,Animal] = cc2.m1(new Animal)c2.m2(new Bird)}
回到顶部

View Bound <%

Scala还有一种视图绑定的功能,如
    class Bird {def sing = {}}class Toy {}class Consumer[T <% Bird]() {def use(t: T) = t.sing}
或者类型参数在方法上:
    class Bird {def sing = {}}class Toy {}class Consumer() {def use[T <% Bird](t: T) = t.sing}class Test extends App {val c = new Consumer()c.use(new Toy)}
它要求T必须有一种隐式转换能转换成Bird,也就是 T => Bird,否则上面的代码会编译出错:No implicit view available from Toy => Bird.加入一个隐式转换,编译通过。
    import scala.language.implicitConversionsclass Bird {def sing = {}}class Toy {}class Consumer() {def use[T <% Bird](t: T) = t.sing}class Test extends App {implicit def toy2Bird(t: Toy) = new Birdval c = new Consumer()c.use(new Toy)}
回到顶部

Context Bound

context bound在Scala 2.8.0中引入,也被称作type class pattern。view bound使用A <% String方式,context bound则需要参数化的类型,如Ordered[A]。它声明了一个类型A,隐式地有一个类型B[A],语法如下:
def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]
更清晰的一个例子:
def f[A : ClassManifest](n: Int) = new Array[A](n)
又比如
def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)
回到顶部

参考

Scala中的协变,逆变,上界,下界等Scala的协变和逆变上界与下界协变点和逆变点
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: