快學Scala第17章----類型參數

本章要點

  • 類、特質、方法和函數都可以有類型參數
  • 將類型參數放置在名稱之后,以方括號括起來。
  • 類型界定的語法為 T <: UpperBound、 T >: LowerBound、 T <% ViewBound、 T : ContextBound
  • 你可以用類型約束來約束另一個方法,比如(implicit ev: T <:< UpperBound)
  • 用+T(協變)來表示某個泛型類的子類型關系和參數T方向一致, 或用-T(逆變)來表示方向相反。
  • 協變適用于表示輸出的類型參數,比如不可變集合中的元素
  • 逆變適用于表示輸入的類型參數,比如函數參數

泛型類

和Java或C++一樣,類和特質可以帶類型參數。在Scala中,使用方括號來定義類型參數:

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

帶有一個或多個類型參數的類是泛型的。


泛型函數

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

類型變量界定

有時, 你需要對類型變量進行限制。

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

這是錯誤的,我們并不知道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必須是Comparable[T]的子類型。這樣我們就可以實例化Pair[java.lang.String],但是不能實例化Pair[java.io.File] 。
你可以為了類型指定一個下界。例如:

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

假定我們有一個Pair[Student],我們應該允許使用一個Person來替換第一個組件。這樣做的結果將會是一個Pair[Person]。通常而言,替換進來的類型必須是原類型的超類型。

def replaceFirst[R >: T] (newFirst: R) = new Pair[R](newFirst, second)
// 或者
def replaceFirst[R >: T] (newFirst: R) = new Pair(newFirst, second)  // 類型推導

**注意: **如果不寫下界:

def replaceFirst[R] (newFirst: R) = new Pair(newFirst, second)

該方法可以編譯通過,但是它將返回Pair[Any].


視圖界定

在面前上界時我們提供了一個示例:

class Pair[T <: Comparable[T]]

但是如果你new一個Pair(4,2), 編譯器會抱怨說Int不是Comparable[Int]的子類型。因為Scala的Int類型沒有實現Comparable,不過RichInt實現了Comparable[Int],同時還有一個從Int到RichInt的隱式轉換。
解決方法是使用“視圖界定”:

class Pair[T <% Comparable[T]]

<% 意味著T可以被隱式轉換成Comparable[T]。
**說明: **用Ordered特質會更好,它在Comparable的基礎上額外提供了關系操作符:

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

上下文界定

視圖界定 T <% V 要求必須存在一個從T到V的隱式轉換,上下文界定的形式為 T:M,其中M是另一個泛型類。它要求必須存在一個類型為M[T]的“隱式值”。例如:

class Pair[T: Ordering]

上述定義要求必須存在一個類型為Ordering[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上下文界定

要實例化一個泛型的Array[T],我們需要一個Manifest[T]對象。 如果你要編寫一個泛型函數來構造泛型數組的話,你需要傳入這個Manifest對象來幫忙。由于它是構造器的隱式參數,可以用上下文界定:

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

多重界定

類型變量可以同時有上界和下界。寫法為:

T >: Lower <: Upper

你不能同時有多個上界或多個下界。不過你可以要求一個類型實現多個特質:

T <: Comparable[T] with Serializable with Cloneable

你也可以有多個視圖界定:

T <% Comparable[T] <% String

你也可以有多個上下文界定:

T : Ordering : Manifest

類型約束

類型約束提供給你的是另一個限定類型的方式。總共有三種關系可供使用:

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] (val first: T, val second: T) {
  def smaller(implicit ev: T <:< Ordered[T]) = {
    if (first < second) first else second
  }
}

在這里你可以構造出Pair[File], 盡管File并不是帶有先后次序的。只有當你調用smaller方法時,才會報錯。
另一個示例是Option類的orNull方法:

val friends = Map("Fred" -> "Barney", ...)
val friendOpt = friends.get("Wilma")
val friendOrNull = friendOpt.orNull  // 要么是String,要么是null

這種做法并不適用于值類型,例如Int。因為orNUll實現帶有約束Null <:< A, 你仍然可以實例化Option[Int],只要你別使用orNull就好了。

類型約束的另一個用途是改進類型推斷。例如:

def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)

當你執行如下代碼:

firstLast(List(1,2,3))

你會得到一個消息,推斷出的類型參數[Nothing, List[Int]]不符合[A, C <: Iterable[A]]。類型推斷器單憑List(1,2,3)無法判斷出A是什么,因為它在同一個步驟中匹配到A和C。解決的方法是首先匹配C,然后在匹配A:

def firstLast[A, C] (it: C) (implicit ev: C <:< Iterable[A]) = (it.head, it.last)

型變

假定我們有一個函數對pair[Person]做某種處理:

def makeFriends(p: Pair[Person])

如果Student是Person的子類,那么可以用Pair[Student]作為參數調用嗎?缺省情況下,這是個錯誤。盡管Student是Person的子類型,但Pair[Student]和Pair[Person]之間沒有任何關系。
如果你想要這樣的關系,則必須在定義Pair類時表明這一點:

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

+號意味著該類型是與T協變的-----也就是說,它與T按痛樣的方向型變。由于Student是Person的子類,Pair[Student]也就是Pair[Person]的子類型。

也可以有另一個方向的型變--逆變。例如:泛型Friend[T],表示希望與類型T的人成為朋友的人。

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

// 有這么一個函數
def makeFriendWith(s: Student, f: Friend[Student]) { f.befriend(s) }

class Person extends Friend[Person]
class Student extends Person
val s = new Student
val p = new Person

函數調用makeFriendWith(s, p)能成功嗎?這是可以的。
這里類型變化 的方向和子類型方向是相反的。Student是Person的子類,但是Friend[Student]是Friend[Person]的超類。這就是逆變。

在一個泛型的類型聲明中,你可以同時使用這兩中型變,例如 單參數函數的類型為Function1[-A, +R]

def friends(students: Array[Atudent], find: Function1[Student, Person]) = {
  for (s <- students) yield find(s)
}

協變和逆變點

從上面可以看出函數在參數上是逆變的,在返回值上則是協變的。通常而言,對于某個對象消費的值適用逆變,而對于產出它的值則適用于協變。 如果一個對象同時是消費和產出值,則類型應該保持不變,這通常適用于可變數據結構。
如果試著聲明一個協變的可變對偶,則是錯誤的,例如:

class Pair[+T] (var first: T, var second: T)  // 錯誤

不過有時這也會妨礙我們做一些本來沒有風險的事情。例如:

class Pair[+T] (var first: T, var second: T) {
  def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)  // error  T出現在了逆變點
}

// 解決方法是給方法加上另一個類型參數:
class Pair[+T] (var first: T, var second: T) {
  def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
}

對象不能泛型

你不能給對象添加類型參數。


類型通配符

在Java中,所有泛型類都是不變的。不過,你可以在使用通配符改變它們的類型。例如:

void makeFriends(Pair<? extends Person> people)   // Java代碼

可以用List<Student>作為參數調用。

你也可以在Scala中使用通配符

def process(people: java.util.List[_ <: Person])  // scala

在Scala中,對于協變的Pair類,不需要通配符。但是假定Pair是不變的:

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

// 可以定義函數:
def makeFriends (p: Pair[_ <: Person])  // 可以用Pair[Student]調用

逆變使用通配符:

import java.util.Comparator
def min[T](p: Pair[T]) (comp: Comparator[_ >: T])
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 變量初始化可以用用 _ 作占位符,賦值為默認值,字符串 null,Float、Int、Double 等為 0var...
    FaDeo_O閱讀 944評論 0 0
  • object 變量可指向任何類的實例,這讓你能夠創建可對任何數據類型進程處理的類。然而,這種方法存在幾個嚴重的問題...
    CarlDonitz閱讀 935評論 0 5
  • 大數據學院_騰訊大數據http://data.qq.com/academySpark是一個通用的并行計算框架,立足...
    葡萄喃喃囈語閱讀 645評論 0 1
  • 室內全彩顯示屏都有哪些? 室內led大屏幕一般分為: 直插型全彩LED顯示屏,一般為戶外和半戶外屏;一種為表面貼裝...
    易事達LED顯示屏閱讀 898評論 0 0
  • 勤工這學期最后一次例會開完了呢,論我為何一直毫無怨言死心踏地的一直跟著我們大部長,人好心靈各種俏,在這里對我們的陳...
    真性情才最吸引人閱讀 237評論 0 0