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

转自:http://fineqtbull.iteye.com/blog/477994#bc2364938app

有位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}的子类型。也就是被参数化类型的泛化方向与参数类型的方向是相反的,因此称为逆变。 

接着看一个例子。函数

Java代码   收藏代码
  1. package fineqtbull.customer  
  2. //出版物类  
  3. class Publication(val title: String)  
  4. //书籍类  
  5. class Book(title: String) extends Publication(title)  
  6. //图书库类  
  7. object Library {  
  8.     //定义图书库内全部的书籍  
  9.     val books: Set[Book] =  
  10.         Set(  
  11.             new Book("Programming in Scala"),  
  12.             new Book("Walden")  
  13.         )  
  14.     //打印全部图书内容,使用外部传入的函数来实现  
  15.     def printBookList(info: Book => AnyRef) {  
  16.         //确认Scala中一个参数的函数其实是Function1特征的实例  
  17.         assert(info.isInstanceOf[Function1[_, _]])  
  18.         //打印  
  19.         for (book <- books)  
  20.             println(info(book))  
  21.     }  
  22.     //打印全部图书内容,使用外部传入的GetInfoAction特征的实例来实现  
  23.     def printBokkListByTrait[P >: Book, R <: AnyRef](  
  24.             action : GetInfoAction[P, R]) {  
  25.         //打印  
  26.         for (book <- books)  
  27.             println(action(book))  
  28.     }  
  29.   
  30. }  
  31. //取得图书内容特征,P类型参数的类型下界是Book,R类型参数的类型上界是AnyRef  
  32. trait GetInfoAction[P >: Book, R <: AnyRef] {  
  33.     //取得图书内容的文本描述,对应()操做符  
  34.    def apply(book : P) : R  
  35. }  
  36. //单例对象,文件的主程序  
  37. object Customer extends Application {  
  38.     //定义取得出版物标题的函数  
  39.     def getTitle(p: Publication): String = p.title  
  40.     //使用函数来打印  
  41.     Library.printBookList(getTitle)  
  42.   
  43.     //使用特征GetInfoAction的实例来打印  
  44.     Library.printBokkListByTrait(new GetInfoAction[Publication, String] {  
  45.             def apply(p: Publication) : String = p.title })  
  46. }  

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

Java代码   收藏代码
  1. trait Function1[-S, +T] {  
  2.   def apply(x: S): T  
  3. }  

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。如此一来,主程序的语句也就彻底正确了。scala

 

接下来讲说类型的上界和下界,它们的含义以下。对象

1) U >: Tblog

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

 

2) S <: Tget

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

 

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

 

那么为何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/

相关文章
相关标签/搜索