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

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

目录 [−]

  1. Java中的协变和逆变
  2. Scala的协变
  3. Scala的逆变
  4. 下界lower bounds
  5. 上界upper bounds
  6. 综合协变,逆变,上界,下界
  7. View Bound <%
  8. Context Bound
  9. 参考文档

Scala中的协变逆变和Java中的协变逆变不一样,看起来更复杂。 本文对Scala中的这些概念做一总结。
首先看几个概念:

  • covariant 协变。使你能够使用比原始指定的类型的子类
  • Contravariance 逆变。使你能够使用比原始指定的类型的父类。
  • Invariance 不变。你只能使用原始指定的类型,不能协变和逆变
  • Upper bounds 上界。
  • Lower bounds 下界。

Java中的协变和逆变

首先我们先回顾一下Java中的协变和逆变,这样我们更容易理解Scala中的协变和逆变。
协变


1

2

3

4

5

6


class Super {

Object getSomething(){}

}

class Sub extends Super {

String getSomething() {}

}

Sub.getSomething()是一个协变类型,因为它的返回类型是Super.getSomething返回类型的子类。

逆变


1

2

3

4

5

6


class Super{

void doSomething(String parameter)

}

class Sub extends Super{

void doSomething(Object parameter)

}

Sub.getSomething()是一个逆变类型,因为它的输入参数是Super.getSomething输入参数的父类。

泛型
泛型中也有协变和逆变。


1

2

3

4

5

6

7

8

9

10


List<String> aList...

List<? extends Object> covariantList = aList;

List<? super String> contravariantList = aList;

covariantList.add("d"); //wrong

Object a = covariantList.get(0);

contravariantList.add("d"); //OK

String b = contravariantList.get(1); //wrong

Object c = contravariantList.get(2);

你可以调用covariantList所有的不需要泛型参数的方法,因为泛型参数必须 extends Object, 但是编译时你不知道它确切的类型。但是你可以调用getter方法,因为返回类型总是符合Object类型。
contravariantList正好相反,你可以调用所有的带泛型参数的方法,因为你明确的可以传入一个String的父类。但是getter方法却不行。

Scala的协变

首先我们需要了解的是子类型(subtyping)。一个类可以是其它类的子类(sub-)或者父类(super-)。我们可以使用数学概念(partial order)来定义:


1

A -> B iff A <: B

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

看下面的例子:


1

2

3

4

5

6

7

8

9

10


class Animal {}

class Bird extends Animal {}

class Consumer[T](t: T) {

}

class Test extends App {

val c:Consumer[Bird] = new Consumer[Bird](new Bird)

val c2:Consumer[Animal] = c

}

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

稍微改一下:


1

2

3

4

5

6

7

8

9

10


class Animal {}

class Bird extends Animal {}

class Consumer[+T](t: T) {

}

class Test extends App {

val c:Consumer[Bird] = new Consumer[Bird](new Bird)

val c2:Consumer[Animal] = c

}

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

Scala的逆变

将上面的例子改一下:


1

2

3

4

5

6

7

8

9

10


class Animal {}

class Bird extends Animal {}

class Consumer[-T](t: T) {

}

class Test extends App {

val c:Consumer[Bird] = new Consumer[Bird](new Bird)

val c2:Consumer[Hummingbird] = c

}

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

下界lower bounds

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


1

2

3

4

5

6


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"。
但是如果返回结果为类型参数则没有问题。


1

2

3

4

5

6


class Animal {}

class Bird extends Animal {}

class Consumer[+T](t: T) {

def get(): T = {new T}

}

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


1

2

3

4

5

6


class Animal {}

class Bird extends Animal {}

class Consumer[+T](t: T) {

def use[U >: T](u : U) = {println(u)}

}

上界upper bounds

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


1

2

3

4

5

6


class Animal {}

class Bird extends Animal {}

class Consumer[-T](t: T) {

def get[U <: T](): U = {new U}

}

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

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

一个综合例子:


1

2

3

4

5

6

7

8

9

10

11

12

13

14


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还有一种视图绑定的功能,如


1

2

3

4

5

6


class Bird {def sing = {}}

class Toy {}

class Consumer[T <% Bird]() {

def use(t: T) = t.sing

}

或者类型参数在方法上:


1

2

3

4

5

6

7

8

9

10

11


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,否则上面的代码会编译出错:


1

No implicit view available from Toy => Bird.

加入一个隐式转换,编译通过。


1

2

3

4

5

6

7

8

9

10

11

12

13

14


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],语法如下:


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)
时间: 2024-10-12 15:52:11

Scala中的协变,逆变,上界,下界等的相关文章

Scala中的协变(+),逆变(-),上界(&lt;:),下界(&gt;:)

对于一个带类型参数的类型,比如 List[T],如果对A及其子类型B,满足 List[B]也符合 List[A]的子类型,那么就称为covariance(协变) ,如果 List[A]是 List[B]的子类型,即与原来的父子关系正相反,则称为contravariance(逆变). 如果一个类型支持协变或逆变,则称这个类型为variance(翻译为可变的或变型),否则称为invariant(不可变的)   在Java里,泛型类型都是invariant,比如 List<String> 并不是 L

c# 泛型协变逆变学习

最近在项目开发当中使用泛型委托Func较多,查看Func的定义就会发现Func的入参都会都会标记上in,出参都会标记上out. in 和out和泛型类型实参有关, 其中in代表逆变,out代表协变.自己协变和逆变在设计接口或者委托的时候也没有定义过, 因此就详细了解一下其用法. 一.关于协变和逆变 在c# 4.0后,泛型类型参数分为以下三种请情况 1.不变量   这带便泛型参数类型参数不能更改 2.协变量  泛型类型参数可以从一个类更改为它的某个基类.c#中使用out关键字标记协变量形式的泛型类

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

前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有区别. 不变.协变和逆变 在.Net4.0之前,泛型接口是不变的..Net4.0通过协变和逆变为泛型接口和泛型委托增加了重要的扩展. 注:本书总体非常好,但在协变和逆变方面,我认为是有缺陷的.我一直偏好通过读书籍来了解技术,而不是逛论坛,但协变和逆变的问题我研究了本书多次,都没搞懂而放弃了,反正平时

C#4.0中的协变和逆变

原文地址 谈谈.Net中的协变和逆变 关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Dog,那么他必然是Animal. 协变逆变正是利用继承关系 对不同参数类型或返回值类型 的委托或者泛型接口之间做转变.我承认这句话很绕,如果你也觉得绕不妨往下看看. 如果一个方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这

[转]C#4.0中的协变和逆变

原文地址 谈谈.Net中的协变和逆变 关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Dog,那么他必然是Animal. 协变逆变正是利用继承关系 对不同参数类型或返回值类型 的委托或者泛型接口之间做转变.我承认这句话很绕,如果你也觉得绕不妨往下看看. 如果一个方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这

.Net中委托的协变和逆变详解

关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Dog,那么他必然是Animal. 协变逆变正是利用继承关系 对不同参数类型或返回值类型 的委托或者泛型接口之间做转变.我承认这句话很绕,如果你也觉得绕不妨往下看看. 如果一个方法要接受Dog参数,那么另一个接受Animal参数的方法肯定也可以接受这个方法的参数,这是Animal向Dog方向的转变是逆变

java逆变与协变(待完善)

协变:若B是A的子类,且F(B)是F(A)的子类,则F为协变 逆变:若B是A的子类,且F(B)是F(A)的父类,则F为逆变 java中的协变:B是A的子类,则List是List的子类 java中的逆变:B是A的子类,则List是List的子类 java中协变与逆变的约束:java中的协变逆变和约束,都是出于对多态的应用. 多态:同一个接口,使用不同的实例执行不同的操作. 协变约束:协变方法支持对传入参数的读操作,但不支持修改操作 逆变约束: 原文地址:https://www.cnblogs.co

这一次,终于弄懂了协变和逆变

一.前言 刘大胖决定向他的师傅灯笼法师请教什么是协变和逆变.   刘大胖:师傅,最近我在学习泛型接口的时候看到了协变和逆变,翻了很多资料,可还是不能完全弄懂. 灯笼法师:阿胖,你不要被这些概念弄混,编译器可不知道你说的什么协变逆变.这个问题,首先你得弄懂什么叫类型的可变性. 刘大胖:可变性? 二.可变性 灯笼法师:对,可变性是以一种类型安全的方式,将一个对象作为另一对象来引用.虽然是可变,但其实对象的引用地址是不会变的,只是忽悠下编译器. 刘大胖:师傅说的将一个对象作为另一对象来引用?这不就是继

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

scala-协变.逆变.上界.下界 当我们定义一个协变类型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 Covarian