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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM