Scala學習筆記 A3/L2篇 - 類型參數(shù) Type Parameters

教材:快學Scala

chapter 17. 類型參數(shù) Type Parameters

類送丰、特質、方法恒削、函數(shù)都可以有類型參數(shù)
類型界定 T <: UpperBound T >: LowerBound T <% ViewBound T : ContextBound
類型約束 implicit ev: T <:< UpperBound
協(xié)變+T適用于表示輸出的類型參數(shù)揖曾,如不可變集合中的元素
逆變-T適用于表示輸入的類型參數(shù),如函數(shù)參數(shù)

17.1 泛型類 Generic Classes

帶有一個或多個類型參數(shù)的類是泛型的
class Pair[T, S](val first: T, val second: S)

17.2 泛型函數(shù) Generic Functions

def getMiddle[T](a: Array[T]) = a(a.length / 2)
val f = getMiddle[String] _ // 具體的函數(shù)悯许,保存到f

17.3 類型變量界定 Bounds for Type Variables

  • 上界 T <: Comparable[T] (T是Comparable[T]的子類型)
    為保證firstcompareTo方法仆嗦,即保證T是Comparable[T]的子類型
class Pair[T <: Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
}

val p = new Pair("Fred", "Brook")
println(p.smaller) // Brook

val p2 = new Pair(4, 2) // 出錯 inferred type arguments [Int] do not conform to class Pair's type parameter bounds [T <: Comparable[T]]
  • 下界 R >: T (T是R的子類型)
    替換Pair的first的方法,對于Pair[Student]希望能用Person實例替換第一個元素先壕,生成新的Pair[Person]
class Pair[T <: Comparable[T]](val first: T, val second: T) {
    def replaceFirst[R >: T](newFirst: R) = new Pair[R](newFirst, second)
}

17.4 視圖界定 View Bounds

  • 視圖界定 T <% Comparable[T] (T可以被隱式轉換成Comparable[T])
    Int可以隱式轉換為RichInt瘩扼,RichInt實現(xiàn)了Comparable[Int],因此Int可以隱式轉換為Comparable[Int]
class Pair[T <% Comparable[T]](val first: T, val second: T) {
    def smaller = if (first.compareTo(second) < 0) first else second
}

val p = new Pair("Fred", "Brook") // 說明子類可以隱式轉換為父類
println(p.smaller) // Brook

val p2 = new Pair(4, 2)
println(p2.smaller) // 2

使用Ordered特質更好垃僚,可以直接用<操作符比較而不是compareTo
java.lang.String 實現(xiàn)了Comparable[String]但沒有實現(xiàn)Ordered[String]故不能用<:
但是java.lang.String 可以隱式轉換為RichString集绰,RichStringOrdered[String]的子類型

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

17.5 上下文界定 Context Bounds

  • 上下文界定 T : Ordering 必須存在一個類型為Ordering[T] 的隱式值
    聲明一個使用隱式值的方法時,需要添加一個隱式參數(shù)(implicit parameter)
    隱式值比隱式轉換更加靈活
class Pari[T : Ordering](val first: T, val second: T) {
    def smaller(implicit ord: Ordering[T]) = // 隱式值 ord
        if (ord.compare(first, second) < 0) first else second
}

17.6 Manifest Context Bound

*what the fuck*
在JVM中冈在,泛型相關的類型信息在編譯期和運行期是被抹掉的倒慧。
Manifest在官方文檔中的說明:

Like scala.reflect.Manifest, TypeTags can be thought of as objects which carry along all type information available at compile time, to runtime. For example, TypeTag[T] encapsulates the runtime type representation of some compile-time type T. Note however, that TypeTags should be considered to be a richer replacement of the pre-2.10 notion of a Manifest, that are additionally fully integrated with Scala reflection.

17.7 多重界定 Multiple Bounds

T >: Lower <: Upper // 同時有上界和下界,但只能各有一個
T <: Comparable[T] with Serializable with Clonable // 可以要求T同時實現(xiàn)多個trait
T <% Comparable[T] <% String // 可以有多個視圖界定
T : Ordering : Manifest // 可以有多個上下文界定

17.8 類型約束 Type Constraints

  • 類型約束 另一種限定類型的方式
    T =:= U // 測試T是否等于U
    T <:< U // 測試T是否為U的子類型
    T <%< U // 測試T能否被視圖(隱式)轉換為U
    使用類型約束時需要添加隱式類型證明參數(shù)(implicit evidence parameter)
    class Pair[T](val first: T, val second: T)(implicit ev: T<:< Comparable[T])
    類型約束之于類型界定的優(yōu)勢:
  1. 類型約束可以在具體的方法約束T,而不是對整個類
    例1.1
class Pair[T](val first: T, val second: T) {
    def smaller(implicit ev: T <:< Ordered[T]) = 
        if (first < second) first else second
}

val p = Pair[File](...) // 只要不調用p.smaller就不會報錯僧凤!

例1.2 Option類的orNull方法
orNull用于將Option值轉換為Java可用的null值和屎,但是對值類型(如Int)是沒有null值的
orNull實現(xiàn)帶有約束Null <:< A,仍然可以實例化Option[Int]只要別使用noNull就行

val friends = Map("Fred" -> "Barney")
val friendOpt = friends.get("Wilma") // Option[String] = None
val friendOrNull = friendOpt.orNull // String = null

val friendStringOpt = friends.get("Fred") // Option[String] = Some(Barney)
val friendStringOrNull = friends.get("Fred").orNull // String = Barney
  1. 類型約束可用于改進類型推斷
def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
firstLast(List(1, 2, 3))
// error: inferred type arguments [Nothing,List[Int]] 
// do not conform to method firstLast's type parameter bounds [A,C <: Iterable[A]]

A是Nothing的原因:單憑List(1,2,3)無法推斷出A是什么遇伞,因為它是在同一個步驟中匹配A和C的
解決方法:先匹配C,再匹配A

def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) = (it.head, it.last)
firstLast(List(1, 2, 3)) // (Int, Int) = (1,3)

17.9 型變 Variance

Student是Person的子類,那么Pair[Student]和Pair[Person]的關系是什么侍郭?
正常來說是沒任何關系的,要添加關系需要用到型變描述

  • 協(xié)變(covariant) Pair[+T] Pair[T]與T按同樣方向型變掠河,Pair[Student]是Pair[Person]的子類
    class Pair[+T](val first: T, val second: T)
  • 逆變(contravariant) Friend[-T] Friend[Person]是Friend[Student]的子類
    trait Friend[-T] { def befriend(someone: T) }
  • 不變(invariant) Pair[T] 沒有任何關系
  • 可以同時使用協(xié)變和逆變亮元,如Function1[-A, +R]
    Function[A, R] 等價于 A => R 即輸入?yún)?shù)為逆變,返回結果為協(xié)變
trait Friend[-T] {
    def befriend(someone: T)
}
def friends(students: Array[Student], find: Function1[Student, Person]) = 
    for (s <- students) yield find(s)

def findStudent(p: Person) : Student = {...}
// findStudent可以作為find的實參
// 輸入:A = Student, A- = Person
// 輸出:R = Person, R+ = Student
...
val arrStuFriends = friends(arrStudents, findStudent) 

17.10 協(xié)變點和逆變點 Co- and Contravariant Positions

對于某個對象消費的值(the values an object consumes)適用逆變(-T)唠摹,對于它產出的值(the values it produces)則適用協(xié)變(+T)爆捞。
如果一個對象同時消費和產出某值,則類型應該是不變(invariant)

  • 逆變點(contravariant position):函數(shù)的參數(shù)位置勾拉,或函數(shù)參數(shù)(function parameter)的返回類型位置
  • 協(xié)變點(covariant position):函數(shù)的返回類型位置煮甥,或函數(shù)參數(shù)(function parameter)的參數(shù)位置
  • 不變點(invariant posision):參數(shù)位置和返回類型位置都同時出現(xiàn)T的方法
    例如Scala中的數(shù)組是invariant盗温,即Array[Student]不能轉換為Array[Person],反過來也不行
    在Java可以將Student[]數(shù)組轉換為Person[]數(shù)組成肘,但如果試圖將非Student對象放入數(shù)組時將跑出ArrayStoreException卖局,在Scala中編譯器會拒絕可能引發(fā)類型錯誤的程序通過編譯。
    參數(shù)位置是逆變點双霍,返回類型的位置是協(xié)變點砚偶;但是函數(shù)參數(shù)則反過來,函數(shù)參數(shù)的參數(shù)是協(xié)變點洒闸,函數(shù)參數(shù)的返回類型的位置是逆變點
// 錯誤:協(xié)變T出現(xiàn)在【first_=所屬的類型T】逆變點
class Pair_t1[+T](var first: T, var second: T)

// 錯誤1:協(xié)變T出現(xiàn)在【replaceFirst所屬的類型(newFirst: T)Pair[T]】不變點
// 錯誤2:協(xié)變T出現(xiàn)在【newFirst所屬的類型T】逆變點
class Pair_t2[+T](val first: T, val second: T) {
    def replaceFirst(newFirst: T) = new Pair[T](newFirst, second)
}

class Pair_t3[+T](val first: T, val second: T) {
    // 正確:引入一個新的類型R并且做類型界定蟹演,此時R是invariant,invariant出現(xiàn)在逆變點沒有問題
    def replaceFirst_t1[R >: T](newFirst: R) = new Pair[R](newFirst, second)
    // 正確:用類型約束也可以
    def replaceFirst_t2[R](newFirst: R)(implicit ev: T <:< R) = new Pair[R](newFirst, second)
}

17.11 Objects Can’t Be Generic

object Empty[T] extends List[T] // 錯誤
解決方法:繼承List[Nothing]顷蟀,因為Nothin是所有類型的子類型

abstract class List[+T] {...}
class Node[T](val head: T, val tail: List[T]) extends List[T] {...}
object Empty extends List[Nothing]
//Empty -> List[Nothing] -> List[T]

val lst = new Node(42, Empty) // 正確

17.12 類型通配符 Wildcards

在Java中酒请,所有泛型類型都是invariant,但可以使用通配符改變類型
Scala也有對應的通配符表示法
Java: void makeFriends(Pair<? extends Person> people) // 可以用List<Student>作為參數(shù)調用
Scala: def makeFriends(people: Pair[_ <: Person])
// 如果Pair是協(xié)變的鸣个,不需要用通配符表示羞反;如果Pair是不變的,可以用通配符囤萤。

練習答案

class Pair[T, S](val first: T, val second: S) { 
    def swap = new Pair(second, first) 
    def p = println(first, second) 
}
val p = new Pair(1, "hello")
p.swap.p
class Pair[T](var first: T, var second: T) {
    def swap = {
        val tmp = first
        first = second
        second = tmp
        this
    }
    def p = println(first, second) 
}
val p = new Pair(1, "world")
p.swap.p
class Pair[T, S](val first: T, val second: S) {
    def p = println(first, second) 
}

// def swap[T, S](p: Pair[T, S]) = {
//     // 
//     def __swap[T, S](first: T)(second: S)(p: Pair[T, S]) = new Pair(p.second, p.first)
//     __swap(p.first)(p.second)(p)
// }
def swap[T, S](p: Pair[T, S]) = new Pair(p.second, p.first)
val p = new Pair(1, "world")
swap(p).p
  1. 里氏替換原則
  2. Int 隱式轉換 RichInt -> Comparable[Int], 即 Int 隱式轉換 Comparable[Int]昼窗,
    只有實現(xiàn)Comparable[Int] 才方便使用視圖界定 T <% Comparable[T]。
    若實現(xiàn)的是Comparable[RichInt]涛舍,則使用視圖界定時要求 T為RichInt澄惊,而整型字面量默認為Int需要先轉換為RichInt不方便。
// def middle[T, I](it: I)(implicit ev: I <:< Iterable[T]): Option[T] = it.take(it.size / 2 + 1).lastOption
// test1: middle(Seq(1, 2, 3, 4, 5)) // Option[Int] = Some(3)
// test2: middle("hello") // error: Cannot prove that String <:< Iterable[T].

// github上看到的解答富雅,暫時不懂隱式類型證明參數(shù)中的=>的含義
def middle[T, I](it: I)(implicit ev: I => Iterable[T]): Option[T] = it.take(it.size / 2 + 1).lastOption
// test1: middle(Seq(1, 2, 3, 4, 5)) // Option[Int] = Some(3)
// test2: middle("hello") // Option[Char] = Some(l)
// error: type mismatch;
// found   : newFirst.type (with underlying type R)
// required: T
class Pair[T](var first: T, var second: T) {
    def replaceFirst[R >: T](newFirst: R) {first = newFirst}
}
class Pair[S, T](var first: S, var second: T) {
    def p = println(first, second) 

    // 寫成 S =:= T 就出錯掸驱,不懂
    def swap(implicit ev: T =:= S) = {
        val tmp = first.asInstanceOf[T]
        first = second
        second = tmp        
        this
    }
}

// 寫成外部函數(shù)就出錯,不懂
// def swap[S, T](p: Pair[S, T])(implicit ev: T =:= S) = {
//     val tmp = p.first.asInstanceOf[T]
//     p.first = p.second
//     p.second = tmp
//     p
// }

val p1 = new Pair(1, 5)
p1.swap.p // (5,1)
val p2 = new Pair(1, "hello")
p2.swap // error: Cannot prove that String =:= Int.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末没佑,一起剝皮案震驚了整個濱河市毕贼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛤奢,老刑警劉巖鬼癣,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啤贩,居然都是意外死亡待秃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門痹屹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來章郁,“玉大人,你說我怎么就攤上這事痢掠∏蹋” “怎么了嘲恍?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雄驹。 經常有香客問我佃牛,道長,這世上最難降的妖魔是什么医舆? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任俘侠,我火速辦了婚禮,結果婚禮上蔬将,老公的妹妹穿的比我還像新娘爷速。我一直安慰自己,他們只是感情好霞怀,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布惫东。 她就那樣靜靜地躺著,像睡著了一般毙石。 火紅的嫁衣襯著肌膚如雪廉沮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天徐矩,我揣著相機與錄音滞时,去河邊找鬼。 笑死滤灯,一個胖子當著我的面吹牛坪稽,可吹牛的內容都是我干的。 我是一名探鬼主播鳞骤,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼窒百,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了弟孟?” 一聲冷哼從身側響起贝咙,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤样悟,失蹤者是張志新(化名)和其女友劉穎拂募,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窟她,經...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡陈症,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了震糖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片录肯。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖吊说,靈堂內的尸體忽然破棺而出论咏,到底是詐尸還是另有隱情优炬,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布厅贪,位于F島的核電站蠢护,受9級特大地震影響,放射性物質發(fā)生泄漏养涮。R本人自食惡果不足惜葵硕,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贯吓。 院中可真熱鬧懈凹,春花似錦、人聲如沸悄谐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爬舰。三九已至威沫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洼专,已是汗流浹背棒掠。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屁商,地道東北人烟很。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像蜡镶,于是被迫代替她去往敵國和親雾袱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內容