该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-09-26
最后修改:2009-10-12
有位je上的同学来短信向我问起了Scala类型参数中协变、逆变、类型上界和类型下界的使用方法和原理,自己虽然也刚学不久,在主要调查了《Programing in Scala》的19章后,试着在下面做一个总结。如有错误之处还请各位指正。 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圈子看看。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-09-26
scala的泛型比java的半生不熟的玩意好多了,就是符号使用上让人感觉蛮伤眼。
|
|
返回顶楼 | |
发表时间:2009-09-28
不错,谢谢了。
我觉得scala是表达能力很强的语言,符号要慢慢习惯。 |
|
返回顶楼 | |
发表时间:2009-09-28
唉,最近工作忙了,没时间学scala咯。
|
|
返回顶楼 | |
发表时间:2009-09-28
最后修改:2009-09-28
|
|
返回顶楼 | |
发表时间:2009-09-28
从一些特性看,scala很美很强大。但从我自己的学习过程来看,还是觉得比较晦涩,特别是类型这部分。对于规规矩矩,习惯一招一式的java开发人员来说,scala的语法、代码组织都显得很突兀、凌乱,没有好的实践指导与规范。 真希望能有人组织一些小范围的活动,在国内推广scala?或推动建立一些国内的交流平台。 |
|
返回顶楼 | |
发表时间:2009-09-28
devworks 写道
恩,是的。<%用于隐式转换中的隐式参数,PrgInScala中21.6章节也有描述。<%称为视图界限(view bound),用于描述隐式参数。如果有(implicit order: T => Order[T]),则[T <% Order[T]]则是它的简写。可以认为<%与<:(类型上界)具有同样的功能,都可以把T替换成Order[T]来使用,<:用的是类型的泛化,而<%使用的是隐式函数对象的调用,所以<%所适用的范围更加广一些。 |
|
返回顶楼 | |
发表时间:2009-09-28
scala 不支持 x = x>10 ? x : 0 这样的表达式,实在可惜.可惜.
|
|
返回顶楼 | |
发表时间:2009-09-28
windywany 写道 scala 不支持 x = x>10 ? x : 0 这样的表达式,实在可惜.可惜.
可以这样写,x = if (x > 10) x else 0,也很简洁的 |
|
返回顶楼 | |
发表时间:2010-03-17
说实话,很不习惯scala的语法规则
|
|
返回顶楼 | |
浏览 5385 次