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

测试源代码

欢迎大家关注: scala工具库 ,里面包含各种库的测试用例和使用说明文档

协变逆变
协变逆变

B是A的子类,A是B的父类。

当我们定义一个协变类型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

cov不能赋值给cov2,因为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

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

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

这里Contravariant[-T]定义成逆变类型,所以Contravariant[Animal]被看作Contravariant[Bird]的子类型,故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] = c

c2.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.implicitConversions

class 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 Bird

val 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的协变和逆变上界与下界

协变点和逆变点

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容