Scala類型參數

要點

  • 類、特質、方法和函數均可以有類型參數。數組

  • 將類型參數放置在名稱以後,以方括號括起來。函數

  • 類型界定的語法爲T<: UpperBoundT>: LowerBoundT<% ViewBound
    T: ContextBound測試

  • 你能夠用類型約束來約束一個方法,好比(implicit ev:T<:<UpperBound)code

  • +T(協變)來表示某個泛型類的子類型關係和參數T方向一致,或用-T(逆變)來表示方向相反。對象

  • 協變適用於表示輸出的類型參數,好比不可變集合中的元素。ci

  • 逆變適用於表示輸入的類型參數,好比函數參數。get

泛型類

JavaC++一致,類和特質能夠帶類型參數。在Scala中,咱們用方括號來定義類型參數。編譯器

class Pair[T, S](val first: T, val second: S)

Scala會從構造參數中推斷出實際類型:虛擬機

val p = new Pair(42, "String")

你也能夠本身指定類型:it

val p = new Pair[Any, Any](42, "String")

泛型函數

函數和方法也能夠帶有類型參數:

def getMiddle[T](a: Array[T]) = a(a.length / 2)

Scala會從調用該方法使用的實際類型來推斷出類型:

val middle = getMiddle(Array("a", "b", "c", "d", "e"))
println(middle) //c

類型變量界定

有時候你須要對類型變量進行限制。考慮這樣一個Pair類型,它要求它的兩個組件類型相同:

class Pair[T](val first: T, val second: T)

如今但願添加一個方法,產出較小的那個值:

class Pair[T](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
  }

這是錯的,由於咱們並不知道first是否有compareTo方法,要解決這個問題,咱們能夠添加一個上界T<:Comparable[T]

class Pair[T <: Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
  }

注意,這至關因而對類型T加了一條限制:T必須是Comparable[T]的子類型。原來給T指定什麼類型均可以,如今就不行了。
你也能夠爲類型指定一個下界。舉例來講,假定咱們想要定義一個方法,用另外一個值替換對偶的第一個組件。咱們的對偶是不可變的,所以咱們須要返回一個新的對偶。

class Person

  class Student extends Person

  class Pair[T](val first: T, val second: T) {
    def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)
  }

假定咱們有一個Pair[Student],咱們應該容許用一個Person來替換第一個組件,實際上這樣是不可行的,所以,經過在函數後面定義下界來實現。

class Pair[T](val first: T, val second: T) {
    def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
  }

一般而言,替換進來的類型必須是原來類型的超類型。爲了清晰,我給返回的對偶也寫了類型參數,實際上不須要。

視圖界定

在前一節,有一個帶上界的示例:

class Pair[T <: Comparable[T]]

若是你試着new一個Pair(4,2),編譯器會抱怨說Int不是Comparable的子類型,ScalaInt類型並無實現Comparable。不過,RichInt實現了Comparable[Int],同時還有一個從IntRichInt的隱式轉換。
解決辦法是使用視圖界定:

class Pair[T <% Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
  }

<% 關係意味着T能夠被隱式轉換成Comparable[Int]

我的理解:不論是類型變量界定仍是視圖界定,實際上都是在限制類型參數T,類型變量界定要求類型參數T必須是上界的子類或者是下界的父類;視圖界定則是要求類型參數T必須可以隱式轉換成「相似上界」的界定,好比上面提到的,Int隱式轉換成RichIntRichIntComparable[Int]的子類。這樣看來,類型變量界定對類型參數的限制比視圖界定對類型參數的限制是更大了。

上下文界定

視圖界定T<%V要求T必須可以隱式轉換到V,上下文界定的形式爲T:M,其中M是另外一個泛型類。它要求必須存在一個類型爲M[T]的「隱式值」。

class Pair[T: Ordering](val first: T, val second: T) {
    def smaller(implicit ord: Ordering[T]) =
      if (ord.compare(first, second) < 0) first else second
  }

Manifest上下文界定

Manifest were added specially to handle arrays
要實例化一個泛型的Array[T],咱們須要一個Manifest[T]對象。要想讓基本類型的數組可以正常工做的話,這是必須的。舉例來講,若是TInt,你會但願虛擬機中對應的是一個int[]數組。在Scala中,Array只不過是類庫提供的一個類,編譯器並不對它作特殊處理。若是你要編寫一個泛型函數來構造泛型數組的話,你須要傳入這個Manifest對象來幫忙。因爲它是構造器的隱式參數,你能夠用上下文界定:

def makePair[T: Manifest](first: T, second: T) = {
    val r = new Array[T](2)
    r(0) = first
    r(1) = second
  }

若是你調用makePair(4,9),編譯器將定位到隱式的Manifest[Int]並實際上調用makePair(4,9)(intManifest)。這樣一來,該方法調用的就是new Array(2)(intManifet),返回基本類型的數組int[2]
爲何搞這麼複雜?在虛擬機中,泛型相關的類型信息是被抹掉的。只會有一個makePair方法,卻要處理全部的類型T

類型約束

類型約束提供的是另外一個限定類型的方式。總共有三種關係可供使用:
T=:=U 測試T是否等於U
T<:<U 測試T是不是U的子類
T<%<U 測試T時能可以視圖隱式轉換爲U
要使用這樣一個約束,須要添加「隱式類型證實參數」:

class Pair[T](val first: T, val second: T)(implicit ev: T <:< Comparable[T])

不過在上面的例子中,使用類型約束並無比類型變量界定class Pair[T<:Comparable[T]]有更多的優勢。不過在某些場景下,類型約束會頗有用。

  • 類型約束讓你能夠在泛型類中定義只能在特定條件下使用的方法,示例以下:

class Pair[T](val first: T, val second: T) {
    def smaller(implicit ev: T <:< Comparable[T]) =
      if (first.compareTo(second) < 0) first else second
  }

  val p1 = new Pair("a", "b") //a

你能夠構造出Pair[File],儘管File並非帶有前後次序的。只有當你調用smaller方法的時候纔會報錯。

型變

假定咱們有一個函數對Pair[Person]作某種處理:

def makeFriends(p: Pair[Person])

若是StudentPerson的子類,咱們用Pair[Student]做爲參數調用makeFriends,這是個錯誤,由於雖然StudentPerson的子類,可是Pair[Student]Pair[Person]一點關係都沒有。若是你想要這樣的關係,則必須在定義Pair類的時候代表這一點:

class Pair[+T](val first: T, val second: T)

+號意味着若是StudentPerson的子類,那麼Pair[Student]也是Pair[Person]的子類。
也能夠有另外一個方向的型變。考慮泛型類型Friend[T],表示但願與類型T的人成爲朋友的人:

trait Friend[-T] {
    def befriend(someone: T)
  }

如今假定有一個函數:

def makeFriendWith(s: Student, f: Friend[Student]) {
    f.befriend(s)
  }

你能用Friend[Person]做爲參數調用它嗎?也就是說,若是你有:

class Person extends Friend[Person]
  class Student extends Person
  val susan = new Student
  val fred = new Person

函數調用makeFriendWith(susan,fred)能成功嗎?看上去應該能夠,由於fred想和任何人叫交朋友,他也必定會和susan交朋友。注意到這個時候,類型變化的方向和子類型方向是相反的。StudentPerson的子類,可是Friend[Student]Friend[Person]的超類。這種狀況下,須要將類型參數聲明爲逆變的。

相關文章
相關標籤/搜索