Scala學習(九)Scala的泛型、上界、下屆
一、泛型
(1)泛型的介紹
泛型用於指定方法或類可以接受任意型別引數,引數在實際使用時才被確定,泛型可以有效地增強程式的適用性,使用泛型可以使得類或方法具有更強的通用性。泛型的典型應用場景是集合及集合中的方法引數,可以說同java一樣,scala中泛型無處不在,具體可以檢視scala的api。
(2)泛型類、泛型方法
泛型類:指定類可以接受任意型別引數。
泛型方法:指定方法可以接受任意型別引數。(3)示例
1)定義泛型類
/* 下面的意思就是表示只要是Comparable就可以傳遞,下面是類上定義的泛型 */ class GenericTest1[T <: Comparable[T]] { def choose(one:T,two:T): T ={ //定義一個選擇的方法 if(one.compareTo(two) > 0) one else two } } class Boy(val name:String,var age:Int) extends Comparable[Boy]{ override def compareTo(o: Boy): Int = { this.age - o.age } } object GenericTestOne{ def main(args: Array[String]): Unit = { val gt = new GenericTest1[Boy] val huangbo = new Boy("huangbo",60) val xuzheng = new Boy("xuzheng",66) val boy = gt.choose(huangbo,xuzheng) println(boy.name) } }
執行結果:
2)定義泛型方法
class GenericTest2{ //在方法上定義泛型 def choose[T <: Comparable[T]](one:T,two:T): T ={ if(one.compareTo(two) > 0) one else two } } class Boy(val name:String,var age:Int) extends Comparable[Boy]{ override def compareTo(o: Boy): Int = { this.age - o.age } } object GenericTestTwo{ def main(args: Array[String]): Unit = { val gt = new GenericTest2 val huangbo = new Boy("huangbo",60) val xuzheng = new Boy("xuzheng",66) val boy = gt.choose(huangbo,xuzheng) println(boy) } }
執行結果:
二、上界和下界
(1)介紹
在指定泛型型別時,有時需要界定泛型型別的範圍,而不是接收任意型別。比如,要求某個泛型型別,必須是某個類的子類,這樣在程式中就可以放心的呼叫父類的方法,程式才能正常的使用與執行。此時,就可以使用上下邊界Bounds的特性;
Scala的上下邊界特性允許泛型型別是某個類的子類,或者是某個類的父類;
(1) S <: T
這是型別上界的定義,也就是S必須是型別T的子類(或本身,自己也可以認為是自己的子類)。
(2) U >: T
這是型別下界的定義,也就是U必須是型別T的父類(或本身,自己也可以認為是自己的父類)。
(2)示例
1)上界示例
參考上面的泛型方法
2)下界示例
class GranderFather
class Father extends GranderFather
class Son extends Father
class Tongxue
object Card{
def getIDCard[T >: Son](person:T): Unit ={
println("OK,交給你了")
}
def main(args: Array[String]): Unit = {
getIDCard[GranderFather](new Father)
getIDCard[GranderFather](new GranderFather)
getIDCard[GranderFather](new Son)
//getIDCard[GranderFather](new Tongxue)//報錯,所以註釋
}
}
執行結果:
三、協變和逆變
對於一個帶型別引數的型別,比如 List[T]:
如果對A及其子型別B,滿足 List[B]也符合 List[A]的子型別,那麼就稱為covariance(協變);
如果 List[A]是 List[B]的子型別,即與原來的父子關係正相反,則稱為contravariance(逆變)。
協變
____ _____________
| | | |
| A | | List[ A ] |
|_____| |_____________|
^ ^
| |
_____ _____________
| | | |
| B | | List[ B ] |
|_____| |_____________|
逆變
____ _____________
| | | |
| A | | List[ B ] |
|_____| |_____________|
^ ^
| |
_____ _____________
| | | |
| B | | List[ A ] |
|_____| |_____________|
在宣告Scala的泛型型別時,“+”表示協變,而“-”表示逆變。
- C[+T]:如果A是B的子類,那麼C[A]是C[B]的子類。
- C[-T]:如果A是B的子類,那麼C[B]是C[A]的子類。
- C[T]:無論A和B是什麼關係,C[A]和C[B]沒有從屬關係。
根據Liskov替換原則,如果A是B的子類,那麼能適用於B的所有操作,都適用於A。讓我們看看這邊Function1的定義,是否滿足這樣的條件。假設Bird是Animal的子類,那麼看看下面兩個函式之間是什麼關係:
def f1(x: Bird): Animal // instance of Function1[Bird, Animal]
def f2(x: Animal): Bird // instance of Function1[Animal, Bird]
在這裡f2的型別是f1的型別的子類。為什麼?
我們先看一下引數型別,根據Liskov替換原則,f1能夠接受的引數,f2也能接受。在這裡f1接受的Bird型別,f2顯然可以接受,因為Bird物件可以被當做其父類Animal的物件來使用。
再看返回型別,f1的返回值可以被當做Animal的例項使用,f2的返回值可以被當做Bird的例項使用,當然也可以被當做Animal的例項使用。
所以我們說,函式的引數型別是逆變的,而函式的返回型別是協變的。
那麼我們在定義Scala類的時候,是不是可以隨便指定泛型型別為協變或者逆變呢?答案是否定的。通過上面的例子可以看出,如果將Function1的引數型別定義為協變,或者返回型別定義為逆變,都會違反Liskov替換原則,因此,Scala規定,協變型別只能作為方法的返回型別,而逆變型別只能作為方法的引數型別。類比函式的行為,結合Liskov替換原則,就能發現這樣的規定是非常合理的。
總結:引數是逆變的或者不變的,返回值是協變的或者不變的。