教材:快學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]的子類型)
為保證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
}
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
集绰,RichString
是Ordered[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 typeT
. Note however, thatTypeTags
should be considered to be a richer replacement of the pre-2.10 notion of aManifest
, 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)勢:
- 類型約束可以在具體的方法約束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
- 類型約束可用于改進類型推斷
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
- 里氏替換原則
- 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.