本章要點(diǎn)
- 類、特質(zhì)、方法和函數(shù)都可以有類型參數(shù)
- 將類型參數(shù)放置在名稱之后,以方括號(hào)括起來(lái)。
- 類型界定的語(yǔ)法為 T <: UpperBound溜歪、 T >: LowerBound、 T <% ViewBound矩父、 T : ContextBound
- 你可以用類型約束來(lái)約束另一個(gè)方法没龙,比如(implicit ev: T <:< UpperBound)
- 用+T(協(xié)變)來(lái)表示某個(gè)泛型類的子類型關(guān)系和參數(shù)T方向一致, 或用-T(逆變)來(lái)表示方向相反陪白。
- 協(xié)變適用于表示輸出的類型參數(shù)颈走,比如不可變集合中的元素
- 逆變適用于表示輸入的類型參數(shù),比如函數(shù)參數(shù)
泛型類
和Java或C++一樣咱士,類和特質(zhì)可以帶類型參數(shù)立由。在Scala中,使用方括號(hào)來(lái)定義類型參數(shù):
class Pair[T, S] (val first: T, val second: S)
帶有一個(gè)或多個(gè)類型參數(shù)的類是泛型的序厉。
泛型函數(shù)
def getMiddle[T](a: Array[T]) = a(a.length / 2)
類型變量界定
有時(shí)锐膜, 你需要對(duì)類型變量進(jìn)行限制。
class Pair[T](val first: T, val second: T) {
def smaller = if (first.compareTo(second) < 0) first else second // error
}
這是錯(cuò)誤的弛房,我們并不知道first是否有compareTo方法道盏。要解決這個(gè)問(wèn)題,我們可以添加一個(gè)上界 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]的子類型文捶。這樣我們就可以實(shí)例化Pair[java.lang.String]荷逞,但是不能實(shí)例化Pair[java.io.File] 。
你可以為了類型指定一個(gè)下界粹排。例如:
class Pair[T] (val first: T, val second: T) {
def peplaceFirst[newFirst: T] = new Pair[T] (newFirst, second)
}
假定我們有一個(gè)Pair[Student]种远,我們應(yīng)該允許使用一個(gè)Person來(lái)替換第一個(gè)組件。這樣做的結(jié)果將會(huì)是一個(gè)Pair[Person]顽耳。通常而言坠敷,替換進(jìn)來(lái)的類型必須是原類型的超類型妙同。
def replaceFirst[R >: T] (newFirst: R) = new Pair[R](newFirst, second)
// 或者
def replaceFirst[R >: T] (newFirst: R) = new Pair(newFirst, second) // 類型推導(dǎo)
**注意: **如果不寫下界:
def replaceFirst[R] (newFirst: R) = new Pair(newFirst, second)
該方法可以編譯通過(guò),但是它將返回Pair[Any].
視圖界定
在面前上界時(shí)我們提供了一個(gè)示例:
class Pair[T <: Comparable[T]]
但是如果你new一個(gè)Pair(4,2)常拓, 編譯器會(huì)抱怨說(shuō)Int不是Comparable[Int]的子類型渐溶。因?yàn)镾cala的Int類型沒(méi)有實(shí)現(xiàn)Comparable,不過(guò)RichInt實(shí)現(xiàn)了Comparable[Int]弄抬,同時(shí)還有一個(gè)從Int到RichInt的隱式轉(zhuǎn)換茎辐。
解決方法是使用“視圖界定”:
class Pair[T <% Comparable[T]]
<% 意味著T可以被隱式轉(zhuǎn)換成Comparable[T]。
**說(shuō)明: **用Ordered特質(zhì)會(huì)更好掂恕,它在Comparable的基礎(chǔ)上額外提供了關(guān)系操作符:
class Pair[T <% Ordered[T]] (val first: T, val second: T) {
def smaller = if( first < second ) first else second
}
上下文界定
視圖界定 T <% V 要求必須存在一個(gè)從T到V的隱式轉(zhuǎn)換拖陆,上下文界定的形式為 T:M,其中M是另一個(gè)泛型類懊亡。它要求必須存在一個(gè)類型為M[T]的“隱式值”依啰。例如:
class Pair[T: Ordering]
上述定義要求必須存在一個(gè)類型為Ordering[T]的隱式值。當(dāng)你聲明一個(gè)使用隱式值的方法時(shí)店枣,你需要添加一個(gè)“隱式參數(shù)”速警。例如:
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上下文界定
要實(shí)例化一個(gè)泛型的Array[T],我們需要一個(gè)Manifest[T]對(duì)象鸯两。 如果你要編寫一個(gè)泛型函數(shù)來(lái)構(gòu)造泛型數(shù)組的話闷旧,你需要傳入這個(gè)Manifest對(duì)象來(lái)幫忙。由于它是構(gòu)造器的隱式參數(shù)钧唐,可以用上下文界定:
def makePair[T: Manifest](first: T, second: T) {
val r = new Array[T](2)
r(0) = first
r(1) = second
r
}
多重界定
類型變量可以同時(shí)有上界和下界忙灼。寫法為:
T >: Lower <: Upper
你不能同時(shí)有多個(gè)上界或多個(gè)下界。不過(guò)你可以要求一個(gè)類型實(shí)現(xiàn)多個(gè)特質(zhì):
T <: Comparable[T] with Serializable with Cloneable
你也可以有多個(gè)視圖界定:
T <% Comparable[T] <% String
你也可以有多個(gè)上下文界定:
T : Ordering : Manifest
類型約束
類型約束提供給你的是另一個(gè)限定類型的方式钝侠「迷埃總共有三種關(guān)系可供使用:
T =:= U // T是否等于U
T <:< U // T是否為U的子類型
T <%< U // T能否被視圖(隱士)轉(zhuǎn)換為U
要使用這樣一個(gè)約束,你需要添加“隱式類型證明參數(shù)”:
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
}
}
在這里你可以構(gòu)造出Pair[File], 盡管File并不是帶有先后次序的里初。只有當(dāng)你調(diào)用smaller方法時(shí),才會(huì)報(bào)錯(cuò)忽舟。
另一個(gè)示例是Option類的orNull方法:
val friends = Map("Fred" -> "Barney", ...)
val friendOpt = friends.get("Wilma")
val friendOrNull = friendOpt.orNull // 要么是String双妨,要么是null
這種做法并不適用于值類型,例如Int萧诫。因?yàn)閛rNUll實(shí)現(xiàn)帶有約束Null <:< A斥难, 你仍然可以實(shí)例化Option[Int],只要你別使用orNull就好了帘饶。
類型約束的另一個(gè)用途是改進(jìn)類型推斷哑诊。例如:
def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
當(dāng)你執(zhí)行如下代碼:
firstLast(List(1,2,3))
你會(huì)得到一個(gè)消息,推斷出的類型參數(shù)[Nothing, List[Int]]不符合[A, C <: Iterable[A]]及刻。類型推斷器單憑List(1,2,3)無(wú)法判斷出A是什么镀裤,因?yàn)樗谕粋€(gè)步驟中匹配到A和C竞阐。解決的方法是首先匹配C,然后在匹配A:
def firstLast[A, C] (it: C) (implicit ev: C <:< Iterable[A]) = (it.head, it.last)
型變
假定我們有一個(gè)函數(shù)對(duì)pair[Person]做某種處理:
def makeFriends(p: Pair[Person])
如果Student是Person的子類,那么可以用Pair[Student]作為參數(shù)調(diào)用嗎暑劝?缺省情況下骆莹,這是個(gè)錯(cuò)誤。盡管Student是Person的子類型担猛,但Pair[Student]和Pair[Person]之間沒(méi)有任何關(guān)系幕垦。
如果你想要這樣的關(guān)系,則必須在定義Pair類時(shí)表明這一點(diǎn):
class Pair[+T] (val first: T, val second: T)
+號(hào)意味著該類型是與T協(xié)變的-----也就是說(shuō)傅联,它與T按痛樣的方向型變先改。由于Student是Person的子類,Pair[Student]也就是Pair[Person]的子類型蒸走。
也可以有另一個(gè)方向的型變--逆變仇奶。例如:泛型Friend[T],表示希望與類型T的人成為朋友的人比驻。
trait Friend[-T] {
def befriend(someone: T)
}
// 有這么一個(gè)函數(shù)
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
函數(shù)調(diào)用makeFriendWith(s, p)能成功嗎该溯?這是可以的。
這里類型變化 的方向和子類型方向是相反的别惦。Student是Person的子類狈茉,但是Friend[Student]是Friend[Person]的超類。這就是逆變步咪。
在一個(gè)泛型的類型聲明中论皆,你可以同時(shí)使用這兩中型變益楼,例如 單參數(shù)函數(shù)的類型為Function1[-A, +R]
def friends(students: Array[Atudent], find: Function1[Student, Person]) = {
for (s <- students) yield find(s)
}
協(xié)變和逆變點(diǎn)
從上面可以看出函數(shù)在參數(shù)上是逆變的猾漫,在返回值上則是協(xié)變的。通常而言感凤,對(duì)于某個(gè)對(duì)象消費(fèi)的值適用逆變悯周,而對(duì)于產(chǎn)出它的值則適用于協(xié)變。 如果一個(gè)對(duì)象同時(shí)是消費(fèi)和產(chǎn)出值陪竿,則類型應(yīng)該保持不變禽翼,這通常適用于可變數(shù)據(jù)結(jié)構(gòu)。
如果試著聲明一個(gè)協(xié)變的可變對(duì)偶族跛,則是錯(cuò)誤的闰挡,例如:
class Pair[+T] (var first: T, var second: T) // 錯(cuò)誤
不過(guò)有時(shí)這也會(huì)妨礙我們做一些本來(lái)沒(méi)有風(fēng)險(xiǎn)的事情。例如:
class Pair[+T] (var first: T, var second: T) {
def replaceFirst(newFirst: T) = new Pair[T](newFirst, second) // error T出現(xiàn)在了逆變點(diǎn)
}
// 解決方法是給方法加上另一個(gè)類型參數(shù):
class Pair[+T] (var first: T, var second: T) {
def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
}
對(duì)象不能泛型
你不能給對(duì)象添加類型參數(shù)礁哄。
類型通配符
在Java中长酗,所有泛型類都是不變的。不過(guò)桐绒,你可以在使用通配符改變它們的類型夺脾。例如:
void makeFriends(Pair<? extends Person> people) // Java代碼
可以用List<Student>作為參數(shù)調(diào)用之拨。
你也可以在Scala中使用通配符
def process(people: java.util.List[_ <: Person]) // scala
在Scala中,對(duì)于協(xié)變的Pair類咧叭,不需要通配符蚀乔。但是假定Pair是不變的:
class Pair[T] (var first: T, var second: T)
// 可以定義函數(shù):
def makeFriends (p: Pair[_ <: Person]) // 可以用Pair[Student]調(diào)用
逆變使用通配符:
import java.util.Comparator
def min[T](p: Pair[T]) (comp: Comparator[_ >: T])