Scala 数据类型的协变、逆变、上边界、下边界
来源:互联网 发布:海军知耻 陆军马鹿 编辑:程序博客网 时间:2024/05/16 07:31
先说说协变和逆变(实际上还有非变)。协变和逆变主要是用来解决参数化类型的泛化问题。由于参数化类型的参数(参数类型)是可变的,当两个参数化类型的参数是继承关系(可泛化),那被参数化的类型是否也可以泛化呢?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 数据类型的协变、逆变、上边界、下边界
- scala 协变和逆变 在函数上的应用
- scala的协变和逆变分析
- scala的协变与逆变、上界与下界
- Scala的模式匹配,以及逆变、协变等
- Java与Scala的协变与逆变
- Scala的协变covariant(+),逆变contravariant(-),上界(<:),下界(>:)
- scala中的协变和逆变
- scala-协变、逆变、上界、下界
- scala入门:逆变与协变
- scala协变与逆变
- Scala之协变和逆变
- scala进阶8-型变:协变、逆变
- Scala教程(十五)Scala的特性逆变与协变
- Scala逆变和协变的实例分析
- Scala中的类、接口及协变和逆变
- Scala List中的上界下界以及逆变、协变
- scala中的上界、下界、协变和逆变
- 优秀工具资料网站
- STM32之I2C
- struts2的体系结构
- 【mysql】mysql 小数转换成百分数查出(保留两位小数百分数)以及怎么使select中嵌套if的使用
- Oracle 11.2.0.1.0 静默安装
- Scala 数据类型的协变、逆变、上边界、下边界
- OC与Swift混编
- UVA 253 Cube painting
- 什么理论?人与人之间的关系
- 利用Oracle分析函数实现多行数据合并为一行
- Java中图片压缩处理
- RadioButton listView实现
- 集体智慧摘要
- css 浮动