`
fineqtbull
  • 浏览: 51433 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类

Scala类型参数中协变(+)、逆变(-)、类型上界(<:)和类型下界(>:)的使用

阅读更多

有位je上的同学来短信向我问起了Scala类型参数中协变、逆变、类型上界和类型下界的使用方法和原理,自己虽然也刚学不久,在主要调查了《Programing in Scala》的19章后,试着在下面做一个总结。如有错误之处还请各位指正。

先说说协变和逆变(实际上还有非变)。协变和逆变主要是用来解决参数化类型的泛化问题。由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?Java中这种情况下是不可泛化的,然而Scala提供了三个选择,即协变、逆变和非变。下面说一下三种情况的含义,首先假设有参数化特征Queue,那它可以有如下三种定义。
1)trait Queue[T] {}
这是非变情况。这种情况下,当类型S是类型A的子类型,则Queue[S]不可认为是Queue[A]的子类型或父类型,这种情况是和Java一样的。

2)trait Queue[+T] {}
这是协变情况。这种情况下,当类型S是类型A的子类型,则Queue[S]也可以认为是Queue[A}的子类型,即Queue[S]可以泛化为Queue[A]。也就是被参数化类型的泛化方向与参数类型的方向是一致的,所以称为协变。

3)trait Queue[-T] {}
这是逆变情况。这种情况下,当类型S是类型A的子类型,则Queue[A]反过来可以认为是Queue[S}的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,所以称为逆变。

接着看一个例子。

package fineqtbull.customer
//出版物类
class Publication(val title: String)
//书籍类
class Book(title: String) extends Publication(title)
//图书库类
object Library {
    //定义图书库内所有的书籍
    val books: Set[Book] =
        Set(
            new Book("Programming in Scala"),
            new Book("Walden")
        )
    //打印所有图书内容,使用外部传入的函数来实现
    def printBookList(info: Book => AnyRef) {
        //确认Scala中一个参数的函数实际上是Function1特征的实例
        assert(info.isInstanceOf[Function1[_, _]])
        //打印
        for (book <- books)
            println(info(book))
    }
    //打印所有图书内容,使用外部传入的GetInfoAction特征的实例来实现
    def printBokkListByTrait[P >: Book, R <: AnyRef](
            action : GetInfoAction[P, R]) {
        //打印
        for (book <- books)
            println(action(book))
    }

}
//取得图书内容特征,P类型参数的类型下界是Book,R类型参数的类型上界是AnyRef
trait GetInfoAction[P >: Book, R <: AnyRef] {
    //取得图书内容的文本描述,对应()操作符
   def apply(book : P) : R
}
//单例对象,文件的主程序
object Customer extends Application {
    //定义取得出版物标题的函数
    def getTitle(p: Publication): String = p.title
    //使用函数来打印
    Library.printBookList(getTitle)

    //使用特征GetInfoAction的实例来打印
    Library.printBokkListByTrait(new GetInfoAction[Publication, String] {
            def apply(p: Publication) : String = p.title })
}

 上例的Library单例对象的printBookList方法使用了函数来取得书籍的内容。在Scala中函数也是对象,上述情况下的函数有一个参数,实际上该参数是如下特征的实例。

trait Function1[-S, +T] {
  def apply(x: S): T
}

printBookList的info参数是Function1类型,而 Function1的-S类型参数是逆变,+T参数是协变。printBookList方法的assert(info.isInstanceOf[Function1[_, _]])语句可以验证这一点。从printBookList方法的定义可以知道,info的S类型参数是Book,T类型参数是AnyRef。然而主函数中使用处则是Library.printBookList(getTitle),getTitle函数中对应的S是Publication,T是String。为什么可以与printBookList原来的定义不一致呢,这就是协变和逆变的威力了。由于-S是逆变,而Publication是Book的父类,所以Publication可以代替(泛化为)Book。由于+T是协变,而String是AnyRef的子类,所以String可以代替(泛化为)AnyRef。如此一来,主程序的语句也就完全正确了。

 

接下来说说类型的上界和下界,它们的含义如下。

1) U >: T

这是类型下界的定义,也就是U必须是类型T的父类(或本身,自己也可以认为是自己的父类)。

 

2) S <: T

这是类型上界的定义,也就是S必须是类型T的子类(或本身,自己也可以认为是自己的子类)。

 

接着使用前面的例子来说明>:和<:的使用方法。printBokkListByTrait方法实现了与printBookList相同的功能,但它是通过传入特征对象来实现的。也就是说,new GetInfoAction[Publication, String] {}和def getTitle(p: Publication): String是等价的,而GetInfoAction定义中使用>:和<:来代替了Function1中+和-。那是由于>:使得Publication可以代替Book,由于<:使得String可以代替AnyRef。

 

那么为什么Function1中的S是逆变而T是协变呢,那是由apply方法的格式而起的。apply方法的参数类型是S决定了S一定是逆变,而返回类型是T则决定了T是协变,这也是Scala语言的强制规定。

我们再来刨根问底一下,那么为什么Scala要有这种规定呢?这实际上和Liskov代替原理有关,它规定T类型是U类型的子类条件是,在U对象出现的所有地方都可以用T对象来代替。同时对于U和T中相同的方法定义,还必须保证T的参数类型需求的比较少,而T的返回类型提供得比较多。从本文的类子来看,参数类型Publication是Book的父类,所以需求的就比Book少;而返回类型String是AnyRef的子类,所提供的就比AnyRef多。以上就是def getTitle(p: Publication): String可以替代info: Book => AnyRef的原因,也是Scala定义协变和逆变规则的理论基础。

 

欢迎来Scala圈子看看。
http://scalagroup.group.iteye.com/

分享到:
评论
11 楼 970655147 2014-12-18  
留名留名,,5年了
10 楼 ifuleyou49 2014-04-20  
assert(info.isInstanceOf[Function1[_, _]]) 

这条语句报错了呀
9 楼 eric851018 2010-03-17  
说实话,很不习惯scala的语法规则
8 楼 fineqtbull 2009-09-28  
windywany 写道
scala 不支持 x = x>10 ? x : 0 这样的表达式,实在可惜.可惜.

可以这样写,x = if (x > 10) x else 0,也很简洁的
7 楼 windywany 2009-09-28  
scala 不支持 x = x>10 ? x : 0 这样的表达式,实在可惜.可惜.
6 楼 fineqtbull 2009-09-28  
devworks 写道

恩,是的。<%用于隐式转换中的隐式参数,PrgInScala中21.6章节也有描述。<%称为视图界限(view bound),用于描述隐式参数。如果有(implicit order: T => Order[T]),则[T <% Order[T]]则是它的简写。可以认为<%与<:(类型上界)具有同样的功能,都可以把T替换成Order[T]来使用,<:用的是类型的泛化,而<%使用的是隐式函数对象的调用,所以<%所适用的范围更加广一些。
5 楼 joachimz 2009-09-28  

从一些特性看,scala很美很强大。但从我自己的学习过程来看,还是觉得比较晦涩,特别是类型这部分。对于规规矩矩,习惯一招一式的java开发人员来说,scala的语法、代码组织都显得很突兀、凌乱,没有好的实践指导与规范。

真希望能有人组织一些小范围的活动,在国内推广scala?或推动建立一些国内的交流平台。
3 楼 徐风子 2009-09-28  
唉,最近工作忙了,没时间学scala咯。
2 楼 23号 2009-09-28  
不错,谢谢了。
我觉得scala是表达能力很强的语言,符号要慢慢习惯。
1 楼 dennis_zane 2009-09-26  
scala的泛型比java的半生不熟的玩意好多了,就是符号使用上让人感觉蛮伤眼。

相关推荐

    scala-java8-compat_2.11-0.7.0-API文档-中英对照版.zip

    赠送jar包:scala-java8-compat_2.11-0.7.0.jar; 赠送原API文档:scala-java8-compat_2.11-0.7.0-...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。 双语对照,边学技术、边学英语。

    scala-java8-compat_2.11-0.7.0-API文档-中文版.zip

    赠送jar包:scala-java8-compat_2.11-0.7.0.jar; 赠送原API文档:scala-java8-compat_2.11-0.7.0-javadoc.jar; 赠送源代码:scala-java...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    scala-2.9.2.jar

    // Contributed by Daniel Gronau import scala.annotation._ trait Func[T] { ... &lt;.#&gt;+++++++++++[&lt;+++++&gt;-]&lt;.&gt;++++++++[&lt;++ +&gt;-]&lt;.+++.------.--------.[-]&gt;++++++++[&lt;++++&gt; -]&lt;+.[-]++++++++++.""") */

    Scala编程详解 第19讲-Scala编程详解:类型参数 共13页.pptx

    - 协变和逆变:类型参数可以标记为协变covariant(+T)或逆变contravariant(-T),影响类型参数在多态中的行为。 - Existential Types:用于处理类型参数的“存在”信息,通常在类型系统复杂的情况下使用。 掌握...

    scala-parser-combinators_2.12-1.1.0-API文档-中英对照版.zip

    赠送jar包:scala-parser-combinators_2.12-1.1.0.jar; 赠送原API文档:scala-parser-combinators_2.12-1.1.0-javadoc.jar; 赠送源代码:scala-parser-...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻

    scala-parser-combinators_2.11-1.0.4-API文档-中英对照版.zip

    赠送jar包:scala-parser-combinators_2.11-1.0.4.jar; 赠送原API文档:scala-parser-combinators_...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。 双语对照,边学技术、边学英语。

    scala-parser-combinators-2.11-1.0.4-API文档-中文版.zip

    赠送jar包:scala-parser-combinators_2.11-1.0.4.jar; 赠送原API文档:scala-parser-combinators_2.11-1.0.4-javadoc.jar; 赠送源代码:scala-parser-...人性化翻译,文档中的代码和结构保持不变,注释和说明精

    scala-xml_2.11-1.0.4-API文档-中英对照版.zip

    赠送jar包:scala-xml_2.11-1.0.4.jar; 赠送原API文档:scala-xml_2.11-1.0.4-javadoc.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。 双语对照,边学技术、边学英语。

    scala-xml_2.11-1.0.1-API文档-中文版.zip

    赠送jar包:scala-xml_2.11-1.0.1.jar; 赠送原API文档:scala-xml_2.11-1.0.1-javadoc.jar; 赠送源代码:scala-xml_2.11-1.0.1-sources...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    scala中,::,+:,:+,:::,+++的区别

    初学scala的人都会被Seq的各种操作符所困惑。下面简单列举一下各个Seq操作符的区别。 4种操作符的区别和联系 (1) :: 该方法被称为construct,意为构造,向队列的头部追加数据,创造新的列表。用法为: x::list,其中x...

    scala和java混合编译

    例如,避免在Scala代码中使用Java的可变集合,因为这可能导致类型不匹配的问题。同样,当Java代码调用Scala代码时,需确保导入正确的Scala类和对象。 测试方面,你可以使用JUnit(Java测试框架)和ScalaTest(Scala...

    scala-xml_2.12-1.0.6-API文档-中文版.zip

    赠送jar包:scala-xml_2.12-1.0.6.jar; 赠送原API文档:scala-xml_2.12-1.0.6-javadoc.jar; 赠送源代码:scala-xml_2.12-1.0.6-sources...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    scala-compiler-2.11.12-API文档-中文版.zip

    赠送jar包:scala-compiler-2.11.12.jar; 赠送原API文档:scala-compiler-2.11.12-javadoc.jar; 赠送源代码:scala-compiler-2.11.12-...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    idea开发spark程序的环境搭建.docx

    &lt;scala.version&gt;2.11.8&lt;/scala.version&gt; &lt;spark.version&gt;2.4.5&lt;/spark.version&gt; &lt;hadoop.version&gt;2.7.1&lt;/hadoop.version&gt; &lt;/properties&gt; &lt;repositories&gt; &lt;repository&gt; &lt;id&gt;scala-tools.org&lt;/id&gt; &lt;name&gt;Scala...

    flink-scala_2.12-1.14.3-API文档-中文版.zip

    赠送jar包:flink-scala_2.12-1.14.3.jar 赠送原API文档:flink-scala_2.12-1.14.3-javadoc.jar 赠送源代码:flink-scala_2.12-1.14.3-...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。

    SpringBoot集成Neo4j图数据库+利用Spark的朴素贝叶斯分类器实现基于电影知识图谱的智能问答系统源码+说明.zip

    #### spark2.4 == &gt;scala2.11 and scala2.12 ```text &lt;!-- https://mvnrepository.com/artifact/org.apache.spark/spark-core --&gt; &lt;dependency&gt; &lt;groupId&gt;org.apache.spark&lt;/groupId&gt; &lt;artifactId&gt;spark-core_...

    scala-compiler-2.11.8-API文档-中英对照版.zip

    赠送jar包:scala-compiler-2.11.8.jar; 赠送原API文档:scala-compiler-2.11.8-javadoc.jar;...人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心使用。 双语对照,边学技术、边学英语。

    Scala编程详解 第7讲-Scala编程详解:函数入门之变长参数 共4页.pptx

    函数入门之默认参数和带名参数 共5页第7讲-Scala编程详解:函数入门之变长参数 共4页第8讲-Scala编程详解:函数入门之过程、lazy值和异常 共5页第9讲-Scala编程详解:数组操作之Array、ArrayBuffer以及遍历数组 共7...

    scala-burst-trie:Scala 中通用 Burst Trie 的实现

    它得到了增强,还可以利用 GWT 的实现中使用的技术。 我在最快的多主机解决方案中在 Stripe 的 CTF 3,级别 3 上使用了这个实现。 Maven 设置 &lt; dependency&gt; &lt; groupId&gt;com.nefariouszhen.trie&lt;/ groupId&gt; &lt; ...

    mongo-scala-drive的使用demo

    在 Scala 中与 MongoDB 进行交互,通常我们会使用 `mongo-scala-driver`,而不是 `mongo-java-driver`,因为 Scala 驱动提供了更符合 Scala 语言特性的 API 设计。本示例将详细介绍如何使用 `mongo-scala-driver` ...

Global site tag (gtag.js) - Google Analytics