为什么80%的码农都做不了架构师?>>>   

刚开始学面向对象的时候,记得有一个设计原则叫LSP原则,简单来说就是设有类型P和P的子类S,假如一个方法接受类型P作为参数,那这个方法也应该能接受S。

这个概念不难理解,java和scala都能接受子类。那么当问题扩展到集合的时候会怎样呢?也就是如果函数接受P实例的集合为参数,它是否能接受P的子类S的集合呢?

如果对这个问题的答案是可以,则称为协变。这个术语其实是来自范畴论的。是个跟集合相关的问题。有兴趣的可以去查查数学方面的本源。

我们研究一下java的集合,从5.0开始java就支持了泛型,我们做下实验

Object s = "abc";

List<Object> objects = new java.util.ArrayList<String>();

第一行OK,第二行编译不过,报:ArrayList<String>无法转换为List<Object>,

说明java是非协变的
那么scala里会怎样呢?

val s:AnyRef = "abc"

var objects:List[AnyRef] = List[String]("abc","123")

全部编译通过。

那么这是否说明scala语言中集合是默认协变的呢?并非如此!  

我们在scala里用java集合类做下实验

val ss:java.lang.Object = new java.lang.String("abc")

var jObjects:java.util.List[Object] =

new java.util.ArrayList[java.lang.String]()

结果是编译不过,而且scala明确给出了提示

<console>:34: error: type mismatch;

found : java.util.ArrayList[java.lang.String]

required: java.util.List[java.lang.Object]

Note: java.lang.String <: java.lang.Object, but Java-defined

trait List is invariant in type E.

明确告知invariant,也就是不协变。

就是说在语言层面上来说scala里是默认非协变的, 查看scala.List的定义可见  class List [+A]

scala提供了更细的语法来控制协变行为,这里是通过[+A]来明确声明List为协变,因此我们的控制粒度细了很多。

协变的可变(mutable)集合其实是不安全的,比如java里历史悠久的数组类型是协变的。

String[] a = new String[] { "abc" };

Object[] b = a;

b[0] = 1;

System.out.println(a[0]);

这些语句能编译通过,但是运行时出错:Exception in thread "main"java.lang.ArrayStoreException: java.lang.Integer at Lsp.main(Lsp.java:24)

既然存在这种不安全问题,那么为什么scala里的List定义为[+A]呢?原因是scala的List是不可变的,对不变集合的修改会产生新的实例而不影响旧实例。我们可以看到scala的可变集合的定义:classMutableList [A]

这个是不可协变的。

除了协变外,scala还可以定义[-A],称为逆变,还有上界和下界,但是在继续前让我设计个稍微有意思点的例子一边帮助思考。

假设有Fruit类和其子类Apple,然后我们有个参数化类型Package[T]可以把东西放进去。现在我们定义一个cut函数,此函数接受Package[Fruit]类型的参数,那么请问它能接受Package[Apple]类型的参数吗?

如果前面看懂了, 那答案很清楚,不能,因为scala是不协变的。看一下代码

class Fruit {

  def selfIntro() = "Fruit"

}

 

class Apple extends Fruit{

  override def selfIntro() = "Apple"

}

 

class Package[T] (val contents:Seq[T]) {

  def this() = this(List[T]())

  def putIn[T](xs:T*) = {new Package(for(x <- xs) yield x) }

  def getAll() = contents

}

def cut(p:Package[Fruit]) {p.getAll foreach (x => Console.println(x.selfIntro + " cutted"))}

 

val fruitPackage = new Package[Fruit].putIn(new Fruit, new Fruit, new Fruit)

val applePackage = new Package[Apple].putIn(new Apple, new Apple, new Apple)

很这个cut只能切fruitPackage,不能切applePackage,尽管Apple是Fruit的子类,而且编译器会很清楚的告诉你原因是因为不协变,并建议你改用+T

修改成协变却不是那么容易的,由于scala实现上的细节,必须利用“下界”来做声明,见代码

class Package[+T] (val contents:Seq[T]) {

  def this() = this(List[T]())

  def putIn[U >: T](xs:U*) = {new Package(for(x <- xs) yield x) }

  def getAll() = contents

}

使用这个修改过的Package,cut就可以操作applePackage了,注意U >: T表示U为T和T

的超类,这个东东叫做下界。

那么逆变呢?Package[-T]与协变相反,就是只能接受Fruit及其超类而不是子类。逆变在集合上

没有什么实际价值,乍听上去也很难理解,但是在设计库的时候逆变也是一个重要的工具。

仍然基于水果的背景,我们考虑如下场景:如果有RedApple extends Apple extends Fruit

有一把切水果刀,就好象料理机那样,装上水果刀头就能切所有的水果,但是比较粗糙,装上

苹果刀就能切苹果,装好红苹果刀就能把红苹果切成艺术品。一个厨师右手拿水果,左手去拿刀,如果

手里拿的是苹果,显然切苹果的刀和切水果的刀都可以用,而切子类红苹果的刀反而不能用了,对吗?

 

class Fruit {

  def selfIntro() = "Fruit"

}

 

class Apple extends Fruit{

  override def selfIntro() = "Apple"

}

 

class RedApple extends Apple{

  override def selfIntro() = "RedApple"

}

 

class Cutter[-] {

  def cut[U <: T](x:U) = x match {

    case (f:Fruit) => Console.println("cut a " ++ f.selfIntro)

    case _ => Console.println("don't know what to do")

  }

}

 

class Chef[T] {

  def cutIt(c:Cutter[T], p:T) = c.cut(p)

}

///测试结果,切红苹果的刀是不能切苹果的,而且水果的刀是可以的,这正是我们需要的

scala> new Chef[Apple].cutIt(new Cutter[RedApple], new Apple)

<console>:16: error: type mismatch;

found : Cutter[RedApple]

required: Cutter[Apple]

              new Chef[Apple].cutIt(new Cutter[RedApple], new Apple)

                                    ^

 

scala> new Chef[Apple].cutIt(new Cutter[Apple], new Apple)

cut a Apple

 

scala> new Chef[Apple].cutIt(new Cutter[Fruit], new Apple)

cut a Apple

可见这正是我们需要的结果,通过精确的类型定义,我们使得编译器能够检查出用户对库的不正确

使用,提高了库的强壮性。

scala的类型系统很复杂,但是对库设计者来说非常强大,还有很多需要学的东西,写这篇博客花了不少时间,

希望对大家有用。

标签 : haskell, scala, 函数式编程, 编程, 软件开发

本文转自大魔头博客: http://www.kaopua.com/blog/2012/01/02/1325438040000.html

http://blog.csdn.net/lord_is_layuping/article/details/7797976

转载于:https://my.oschina.net/liango/blog/69662

scala中的协变和逆变相关推荐

  1. spark笔记之Scala中的协变、逆变、非变

    1.1. 协变.逆变.非变介绍 协变和逆变主要是用来解决参数化类型的泛化问题.Scala的协变与逆变是非常有特色的,完全解决了Java中泛型的一大缺憾:举例来说,Java中,如果有 A是 B的子类,但 ...

  2. 【软件构造】--Java中的协变与逆变

    提示:本文主要讨论Java中的协变与逆变 Java中的协变与逆变 前言 一.Liskov替换原则(LSP) 二.协变(Covariance)和逆变(Contravariance) 1.概念 三 讨论 ...

  3. typescript中的协变、逆变

    typescript中的协变.逆变 ​ 想要写出更加优秀的类型编程co-variance(协变)contra-variance(逆变)这类知识是我们必须掌握的.这篇记录也仅仅是为了方便之后哪天这块知识 ...

  4. C# 4.0中的协变和逆变(一)

    在刚刚落下帷幕的PDC上,我们得到了很多振奋的消息,包括C# 4.0及VS2010等等.Anders Liu 已经 将C# 4.0 新特性白皮书翻译了 出来,那里面有非常详细的介绍. C#的发展是很快 ...

  5. 协变逆变java_Java中的协变与逆变

    Java作为面向对象的典型语言,相比于C++而言,对类的继承和派生有着更简洁的设计(比如单根继承). 在继承派生的过程中,是符合Liskov替换原则(LSP)的.LSP总结起来,就一句话: 所有引用基 ...

  6. Scala泛型:协变和逆变

  7. Scala教程之:深入理解协变和逆变

    文章目录 函数的参数和返回值 可变类型的变异 在之前的文章中我们简单的介绍过scala中的协变和逆变,我们使用+ 来表示协变类型:使用-表示逆变类型:非转化类型不需要添加标记. 假如我们定义一个cla ...

  8. C#中协变和逆变的基本概念、List和List.Select方法的命名空间

    在 C# 中,协变和逆变能够实现数组类型.委托类型和泛型类型参数的隐式引用转换. 协变保留分配兼容性,逆变则与之相反. msdn 解释如下: "协变"是指能够使用与原始指定的派生类 ...

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

    关于协变和逆变要从面向对象继承说起.继承关系是指子类和父类之间的关系:子类从父类继承所以子类的实例也就是父类的实例.比如说Animal是父类,Dog是从Animal继承的子类:如果一个对象的类型是Do ...

  10. java协变 生产者理解_Java进阶知识点:协变与逆变

    一.背景 要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那 ...

最新文章

  1. JAVA 多线程实现包子铺(买包子,吃包子)
  2. iOS学习资源(二)
  3. 这里有最全的k8s初学者指南!!!
  4. python读取大文件-强悍的Python读取大文件的解决方案
  5. 热烈欢呼:cnblogs.com博客园首页通过W3C验证
  6. RDIFramework.NET ━ .NET快速信息化系统开发框架-4.3 平台主界面
  7. 单词搭配用法查询网站
  8. php后台登录显示ok,thinkphp的项目 后台登录问题,怪事
  9. Android 系统(140)---android.mk中几个常见配置
  10. MTK:文件操作接口详解
  11. Poj 2421 Constructing Roads(Prim 最小生成树)
  12. 深入浅出AOP(一)
  13. 五人表决器课程设计单片机c语言,基于单片机的五人表决器的设计.doc
  14. 平面设计专业介绍,平面设计专业有哪些课程
  15. 为什么使用工作流引擎,什么是工作流引擎,工作流引擎选型以及如何使用
  16. 有哪些比较好用的开源项目管理工具?
  17. Resize operation completed for file#
  18. 统信UOS应用商店十月活动
  19. Visual Studio Code 配置C/C++编译环境流程及问题解决(Win10环境)
  20. L48.linux命令每日一练 -- 第七章 Linux用户管理及用户信息查询命令 -- last、lastb和lastlog

热门文章

  1. 在spring中手动编写事务
  2. 872. Leaf-Similar Trees - LeetCode
  3. hdu-5754 Life Winner Bo(博弈)
  4. unity3d 射击游戏BOSS行为代码
  5. 也谈“避免使用虚函数作为库的接口”
  6. Spring动态的切换数据源
  7. 「日常训练」Queue(Codeforces Round 303 Div.2 D)
  8. JavaScript常见设计模式梳理
  9. 解决permission denied错误
  10. Mac电脑下配置maven环境变量