scala-协变、逆变、上界、下界

当我们定义一个协变类型List[A+]时,List[Child]可以是List[Parent]的子类型。

当我们定义一个逆变类型List[-A]时,List[Child]可以是List[Parent]的父类型。

Scala的协变

看下面的例子:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Animal {}
  4. class Bird extends Animal {}
  5. //协变
  6. class Covariant[T](t:T){}
  7. val cov = new Covariant[Bird](new Bird)
  8. val cov2:Covariant[Animal] = cov

c不能赋值给c2,因为Covariant定义成不变类型。

稍微改一下:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Animal {}
  4. class Bird extends Animal {}
  5. //协变
  6. class Covariant[+T](t:T){}
  7. val cov = new Covariant[Bird](new Bird)
  8. val cov2:Covariant[Animal] = cov

因为Consumer定义成协变类型的,所以Covariant[Bird]是Covariant[Animal]的子类型,所以它可以被赋值给c2。

Scala的逆变

将上面的例子改一下:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Contravariant[-T](t: T) {
  4. }
  5. val c: Contravariant[Animal] = new Contravariant[Animal](new Animal)
  6. val c2: Contravariant[Bird] = c

这里Consumer[-T]定义成逆变类型,所以Contravariant[Animal]被看作Contravariant[Animal]的子类型,故c可以被赋值给c2。

下界lower bounds

如果协变类包含带类型参数的方法时:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Consumer[+T](t: T) {
  4. def use(t: T) = {}
  5. }

编译会出错。出错信息为 "Covariant type T occurs in contravariant position in type T of value t"。
但是如果返回结果为类型参数则没有问题。

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Consumer[+T](t: T) {
  4. def get(): T = {new T}
  5. }

为了在方法的参数中使用类型参数,你需要定义下界:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Consumer[+T](t: T) {
  4. def use[U >: T](u : U) = {println(u)}
  5. }

这个地方比较复杂, 简单的说就是Scala内部实现是, 把类中的每个可以放类型的地方都做了分类(+, –, 中立), 具体分类规则不说了 对于这里最外层类[+T]是协变, 但是到了方法的类型参数时, 该位置发生了翻转, 成为-逆变的位置, 所以你把T给他, 就会报错说你把一个协变类型放到了一个逆变的位置上

所以这里的处理的方法就是, 他要逆变, 就给他个逆变, 使用[U >: T], 其中T为下界, 表示T或T的超类, 这样Scala编译器就不报错了

上界upper bounds

看一下逆变类中使用上界的例子:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Consumer[-T](t: T) {
  4. def get[U <: T](): U = {new U}
  5. }

可以看到方法的返回值是协变的位置,方法的参数是逆变的位置。
因此协变类的类型参数可以用在方法的返回值的类型,在方法的参数类型上必须使用下界绑定 >:。
逆变类的类型参数可以用在方法的参数类型上,用做方法的返回值类型时必须使用上界绑定 <:。

综合协变,逆变,上界,下界

一个综合例子:

  1. class Animal {}
  2. class Bird extends Animal {}
  3. class Consumer[-S,+T]() {
  4. def m1[U >: T](u: U): T = {new T} //协变,下界
  5. def m2[U <: S](s: S): U = {new U} //逆变,上界
  6. }
  7. class Test extends App {
  8. val c:Consumer[Animal,Bird] = new Consumer[Animal,Bird]()
  9. val c2:Consumer[Bird,Animal] = c
  10. c2.m1(new Animal)
  11. c2.m2(new Bird)
  12. }

View Bound <%

Scala还有一种视图绑定的功能,如

  1. class Bird {def sing = {}}
  2. class Toy {}
  3. class Consumer[T <% Bird]() {
  4. def use(t: T) = t.sing
  5. }

或者类型参数在方法上:

  1. class Bird {def sing = {}}
  2. class Toy {}
  3. class Consumer() {
  4. def use[T <% Bird](t: T) = t.sing
  5. }
  6. class Test extends App {
  7. val c = new Consumer()
  8. c.use(new Toy)
  9. }

它要求T必须有一种隐式转换能转换成Bird,也就是 T => Bird,否则上面的代码会编译出错:
No implicit view available from Toy => Bird.
加入一个隐式转换,编译通过。

  1. import scala.language.implicitConversions
  2. class Bird {def sing = {}}
  3. class Toy {}
  4. class Consumer() {
  5. def use[T <% Bird](t: T) = t.sing
  6. }
  7. class Test extends App {
  8. implicit def toy2Bird(t: Toy) = new Bird
  9. val c = new Consumer()
  10. c.use(new Toy)
  11. }

Context Bound

context bound在Scala 2.8.0中引入,也被称作type class pattern。
view bound使用A <% String方式,context bound则需要参数化的类型,如Ordered[A]。
它声明了一个类型A,隐式地有一个类型B[A],语法如下:

  1. def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

更清晰的一个例子:

  1. def f[A : ClassManifest](n: Int) = new Array[A](n)

又比如

  1. def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)

参考

Scala中的协变,逆变,上界,下界等

Scala的协变和逆变上界与下界

协变点和逆变点

scala-协变、逆变、上界、下界的更多相关文章

  1. 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4

    前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...

  2. C#中泛型方法与泛型接口 C#泛型接口 List<IAll> arssr = new List<IAll>(); interface IPerson<T> c# List<接口>小技巧 泛型接口协变逆变的几个问题

    http://blog.csdn.net/aladdinty/article/details/3486532 using System; using System.Collections.Generi ...

  3. C#的in/out关键字与协变逆变

    C#提供了一组关键字in&out,在泛型接口和泛型委托中,若不使用关键字修饰类型参数T,则该类型参数是不可变的(即不允许协变/逆变转换),若使用in修饰类型参数T,保证"只将T用于输 ...

  4. Programming In Scala笔记-第十九章、类型参数,协变逆变,上界下界

    本章主要讲Scala中的类型参数化.本章主要分成三个部分,第一部分实现一个函数式队列的数据结构,第二部分实现该结构的内部细节,最后一个部分解释其中的关键知识点.接下来的实例中将该函数式队列命名为Que ...

  5. java协变逆变,PECS

    public static void main(String[] args) { // Object <- Fruit <- Apple <- RedApple System.out ...

  6. 协变 & 逆变

    都跟里氏替换原则有关. 协变:你可以用一个子类对象去替换相应的一个父类对象,这是完全符合里氏替换原则的,和协(谐)的变.如:用Swan替换Bird. 逆变:你可以用一个父类对象去替换相应的一个子类对象 ...

  7. C#核心语法讲解-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...

  8. C#核心语法-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)

    泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...

  9. Scala中的协变,逆变,上界,下界等

    Scala中的协变,逆变,上界,下界等 目录 [−] Java中的协变和逆变 Scala的协变 Scala的逆变 下界lower bounds 上界upper bounds 综合协变,逆变,上界,下界 ...

随机推荐

  1. POJ 1984 Navigation Nightmare 带全并查集

    Navigation Nightmare   Description Farmer John's pastoral neighborhood has N farms (2 <= N <= ...

  2. ubuntu12.04 安装eclipse

    1:去官网下载最新版的eclipse for linux; 2:cd  /usr/local 用命令 sudo mkdir eclipse 建立一个Eclipse的目录 3:将下载的文件copy到ec ...

  3. UI背景构建

    个中原因不是很明白 <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android=& ...

  4. jQuery基础知识点(DOM操作)

    1.样式属性操作     1)设置样式属性操作         ①设置单个样式: // 第一个参数表示:样式属性名称 // 第二个参数表示:样式属性值 $(selector).css(“color”, ...

  5. JavaWeb监听器详解

    1 JavaWeb监听器概述 在JavaWeb被监听的事件源为:ServletContext.HttpSession.ServletRequest,即三大域对象.有监听域对象"创建" ...

  6. kylin学习笔记

    阅读官网,学到哪就写到哪 1.需要先建立Model 2.kylin需要配置事实表,纬度表:可以自定义join.  我的用法和官方建议的不同,我是直接在hive中将所有的取join成一个单表,再根据单表 ...

  7. Liferay 6.2 改造系列之三:删除Docbar中的添加内容功能

    在/portal-master/portal-web/docroot/html/portlet/dockbar/add_panel.jsp文件中 将以下内容: if (hasAddContentAnd ...

  8. MVVM模式下实现拖拽

    在文章开始之前先看一看效果图 我们可以拖拽一个"游戏"给ListBox,并且ListBox也能接受拖拽过来的数据, 但是我们不能拖拽一个"游戏类型"给它. 所以 ...

  9. super一些要点

    package o6; class Grandparent { public Grandparent() { System.out.println("GrandParent Created. ...

  10. @import与link

    本质上,这两种方式都是为了加载CSS文件,但还是存在着细微的差别. 1. 老祖宗的差别.link属于XHTML标签,而@import完全是CSS提供的一种方式. link标签除了可以加载CSS外,还可 ...