Scala中的协变,逆变,上界,下界等
Scala中的协变,逆变,上界,下界等
目录 [−]
Scala中的协变逆变和Java中的协变逆变不一样,看起来更复杂。 本文对Scala中的这些概念做一总结。
首先看几个概念:
- covariant 协变。使你能够使用比原始指定的类型的子类
- Contravariance 逆变。使你能够使用比原始指定的类型的父类。
- Invariance 不变。你只能使用原始指定的类型,不能协变和逆变
- Upper bounds 上界。
- Lower bounds 下界。
Java中的协变和逆变
首先我们先回顾一下Java中的协变和逆变,这样我们更容易理解Scala中的协变和逆变。
1 协变
1
2
3
4
5
6
|
class Super {
Object getSomething(){}
}
class Sub extends Super {
String getSomething() {}
}
|
Sub.getSomething()是一个协变类型,因为它的返回类型是Super.getSomething返回类型的子类。
2 逆变
1
2
3
4
5
6
|
class Super{
void doSomething(String parameter)
}
class Sub extends Super{
void doSomething(Object parameter)
}
|
Sub.getSomething()是一个逆变类型,因为它的输入参数是Super.getSomething输入参数的父类。
3 泛型
泛型中也有协变和逆变。
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)
|
Scala中的协变,逆变,上界,下界等的更多相关文章
- 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 ...
- 解读经典《C#高级编程》最全泛型协变逆变解读 页127-131.章4
前言 本篇继续讲解泛型.上一篇讲解了泛型类的定义细节.本篇继续讲解泛型接口. 泛型接口 使用泛型可定义接口,即在接口中定义的方法可以带泛型参数.然后由继承接口的类实现泛型方法.用法和继承泛型类基本没有 ...
- C#的in/out关键字与协变逆变
C#提供了一组关键字in&out,在泛型接口和泛型委托中,若不使用关键字修饰类型参数T,则该类型参数是不可变的(即不允许协变/逆变转换),若使用in修饰类型参数T,保证"只将T用于输 ...
- Programming In Scala笔记-第十九章、类型参数,协变逆变,上界下界
本章主要讲Scala中的类型参数化.本章主要分成三个部分,第一部分实现一个函数式队列的数据结构,第二部分实现该结构的内部细节,最后一个部分解释其中的关键知识点.接下来的实例中将该函数式队列命名为Que ...
- .NET 4.0中的泛型逆变和协变
转载自:http://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html:自己加了一些理解 随Visual Studi ...
- java协变逆变,PECS
public static void main(String[] args) { // Object <- Fruit <- Apple <- RedApple System.out ...
- 协变 & 逆变
都跟里氏替换原则有关. 协变:你可以用一个子类对象去替换相应的一个父类对象,这是完全符合里氏替换原则的,和协(谐)的变.如:用Swan替换Bird. 逆变:你可以用一个父类对象去替换相应的一个子类对象 ...
- C#核心语法讲解-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...
- C#核心语法-泛型(详细讲解泛型方法、泛型类、泛型接口、泛型约束,了解协变逆变)
泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.NET框架引入了类型参数(type parameters)的概念.类型参数使得设计类和方法时,不必确定一个或多个具 ...
随机推荐
- Socket编程的UDP与TCP,应用在哪些地方
随着网络技术飞速发展,网速已不再是传输的瓶颈,UDP协议以其简单.传输快的优势,在越来越多场景下取代了TCP,如网页浏览.流媒体.实时游戏.物联网. 1,网速的提升给UDP稳定性提供可靠网络保障 CD ...
- C# SpinWait 实现
其实SpinWait的code 非常简单,以前看过很多遍,但是从来都没有整理过,整理也是再次学习吧. 我们先看看SpinWait的一些评论或者注意点吧:如果等待某个条件满足需要的时间很短,而且不希望发 ...
- 微软BI 之SSIS 系列 - 带有 Header 和 Trailer 的不规则的平面文件输出处理技巧
案例背景与需求介绍 之前做过一个美国的医疗保险的项目,保险提供商有大量的文件需要发送给比如像银行,医疗协会,第三方服务商等.比如像与银行交互的 ACH 文件,传送给协会的 ACH Credit 等文件 ...
- app:processDebugResources
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:processDebugResources'. ...
- oracle访问不同用户的表不添加用户名前缀
问题的出现,是技术的不断推进.为了更好地实现价值的提升! 01.用视图 create view tab1 select * from B.tab1; //在A用户下执行 02.同义词 gr ...
- Andriod书籍准备
老大说公司准备开发MFC项目,过了一段时间又说开发Andriod,好吧,我现在准备Andriod. 鬼知道过段时间会变成什么. http://pan.baidu.com/share/link?shar ...
- Hadoop2.2.0分布式安装配置详解[2/3]
前言 本文主要通过对hadoop2.2.0集群配置的过程加以梳理,所有的步骤都是通过自己实际测试.文档的结构也是根据自己的实际情况而定,同时也会加入自己在实际过程遇到的问题.搭建环境过程不重要,重要点 ...
- 【概念原理】四种SQL事务隔离级别和事务ACID特性
摘要: SQL事务隔离级别和事务的ACID特性 事务是一组读写操作,并且具有只有所有操作都成功才算成功的特性. 事务隔离级别 SQL事务隔离级别由弱到强分别是:READ_UNCOMMITTED.R ...
- asp.net 逻辑操作符与(&&),或(||),非(!)
逻辑操作符与(&&),或(||),非(!)能根据参数的关系返回布尔值 public class bool{ public static void main(string [] args ...
- 微信公众号 几种移动端UI框架介绍
微信公众号开发,主要是移动端网页的页面开发,在这里推荐3个移动端UI框架:WeUI.SUI和Mint UI. 1. WeUI 1.1 WeUI WeUI是微信官方设计团队为微信 Web 开发量身设计, ...