快學(xué)Scala第17章----類型參數(shù)

本章要點(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])
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市菲茬,隨后出現(xiàn)的幾起案子吉挣,更是在濱河造成了極大的恐慌,老刑警劉巖婉弹,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件听想,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡马胧,警方通過(guò)查閱死者的電腦和手機(jī)佩脊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門出牧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)舔痕,“玉大人豹缀,你說(shuō)我怎么就攤上這事邢笙⌒ト纾” “怎么了叮雳?”我有些...
    開(kāi)封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)寞焙。 經(jīng)常有香客問(wèn)我晶密,道長(zhǎng)模她,這世上最難降的妖魔是什么稻艰? 我笑而不...
    開(kāi)封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任畜侦,我火速辦了婚禮,結(jié)果婚禮上擅羞,老公的妹妹穿的比我還像新娘减俏。我一直安慰自己怕篷,他們只是感情好漫谷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般公给。 火紅的嫁衣襯著肌膚如雪蔫缸。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天街望,我揣著相機(jī)與錄音校翔,去河邊找鬼。 笑死灾前,一個(gè)胖子當(dāng)著我的面吹牛防症,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哎甲,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼告希,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了烧给?” 一聲冷哼從身側(cè)響起燕偶,我...
    開(kāi)封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎础嫡,沒(méi)想到半個(gè)月后指么,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榴鼎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年伯诬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巫财。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盗似,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出平项,到底是詐尸還是另有隱情赫舒,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布闽瓢,位于F島的核電站接癌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扣讼。R本人自食惡果不足惜缺猛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荔燎,春花似錦耻姥、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至摔吏,卻和暖如春鸽嫂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背征讲。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工据某, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人诗箍。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓癣籽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親滤祖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子筷狼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 變量初始化可以用用 _ 作占位符,賦值為默認(rèn)值匠童,字符串 null埂材,F(xiàn)loat、Int汤求、Double 等為 0var...
    FaDeo_O閱讀 902評(píng)論 0 0
  • object 變量可指向任何類的實(shí)例俏险,這讓你能夠創(chuàng)建可對(duì)任何數(shù)據(jù)類型進(jìn)程處理的類。然而扬绪,這種方法存在幾個(gè)嚴(yán)重的問(wèn)題...
    CarlDonitz閱讀 910評(píng)論 0 5
  • 大數(shù)據(jù)學(xué)院_騰訊大數(shù)據(jù)http://data.qq.com/academySpark是一個(gè)通用的并行計(jì)算框架竖独,立足...
    葡萄喃喃囈語(yǔ)閱讀 584評(píng)論 0 1
  • 室內(nèi)全彩顯示屏都有哪些? 室內(nèi)led大屏幕一般分為: 直插型全彩LED顯示屏挤牛,一般為戶外和半戶外屏;一種為表面貼裝...
    易事達(dá)LED顯示屏閱讀 801評(píng)論 0 0
  • 勤工這學(xué)期最后一次例會(huì)開(kāi)完了呢莹痢,論我為何一直毫無(wú)怨言死心踏地的一直跟著我們大部長(zhǎng),人好心靈各種俏墓赴,在這里對(duì)我們的陳...
    真性情才最吸引人閱讀 231評(píng)論 0 0