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/

相關文章
相關標籤/搜索