1. 程式人生 > >Scala學習(九)Scala的泛型、上界、下屆

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替換原則,就能發現這樣的規定是非常合理的。

總結:引數是逆變的或者不變的,返回值是協變的或者不變的。