一、泛型
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) U >: T
這是類型下界的定義,也就是U必須是類型T的父類(或本身,自己也可以認為是自己的父類)。
(2) S <: T
這是類型上界的定義,也就是S必須是類型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替換原則,就能發現這樣的規定是非常合理的。
總結:參數是逆變的或者不變的,返回值是協變的或者不變的。