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

教材:快學(xué)Scala

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

類、特質(zhì)、方法、函數(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

帶有一個(gè)或多個(gè)類型參數(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) // 出錯(cuò) inferred type arguments [Int] do not conform to class Pair's type parameter bounds [T <: Comparable[T]]
  • 下界 R >: T (T是R的子類型)
    替換Pair的first的方法,對(duì)于Pair[Student]希望能用Person實(shí)例替換第一個(gè)元素,生成新的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可以被隱式轉(zhuǎn)換成Comparable[T])
    Int可以隱式轉(zhuǎn)換為RichInt,RichInt實(shí)現(xiàn)了Comparable[Int],因此Int可以隱式轉(zhuǎn)換為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") // 說明子類可以隱式轉(zhuǎn)換為父類
println(p.smaller) // Brook

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

使用Ordered特質(zhì)更好,可以直接用<操作符比較而不是compareTo
java.lang.String 實(shí)現(xiàn)了Comparable[String]但沒有實(shí)現(xiàn)Ordered[String]故不能用<:
但是java.lang.String 可以隱式轉(zhuǎn)換為RichStringRichStringOrdered[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 必須存在一個(gè)類型為Ordering[T] 的隱式值
    聲明一個(gè)使用隱式值的方法時(shí),需要添加一個(gè)隱式參數(shù)(implicit parameter)
    隱式值比隱式轉(zhuǎn)換更加靈活
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中,泛型相關(guān)的類型信息在編譯期和運(yùn)行期是被抹掉的。
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 // 同時(shí)有上界和下界,但只能各有一個(gè)
T <: Comparable[T] with Serializable with Clonable // 可以要求T同時(shí)實(shí)現(xiàn)多個(gè)trait
T <% Comparable[T] <% String // 可以有多個(gè)視圖界定
T : Ordering : Manifest // 可以有多個(gè)上下文界定

17.8 類型約束 Type Constraints

  • 類型約束 另一種限定類型的方式
    T =:= U // 測試T是否等于U
    T <:< U // 測試T是否為U的子類型
    T <%< U // 測試T能否被視圖(隱式)轉(zhuǎn)換為U
    使用類型約束時(shí)需要添加隱式類型證明參數(shù)(implicit evidence parameter)
    class Pair[T](val first: T, val second: T)(implicit ev: T<:< Comparable[T])
    類型約束之于類型界定的優(yōu)勢:
  1. 類型約束可以在具體的方法約束T,而不是對(duì)整個(gè)類
    例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](...) // 只要不調(diào)用p.smaller就不會(huì)報(bào)錯(cuò)!

例1.2 Option類的orNull方法
orNull用于將Option值轉(zhuǎn)換為Java可用的null值,但是對(duì)值類型(如Int)是沒有null值的
orNull實(shí)現(xiàn)帶有約束Null <:< A,仍然可以實(shí)例化Option[Int]只要?jiǎng)e使用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. 類型約束可用于改進(jìn)類型推斷
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是什么,因?yàn)樗窃谕粋€(gè)步驟中匹配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]的關(guān)系是什么?
正常來說是沒任何關(guān)系的,要添加關(guān)系需要用到型變描述

  • 協(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] 沒有任何關(guān)系
  • 可以同時(shí)使用協(xié)變和逆變,如Function1[-A, +R]
    Function[A, R] 等價(jià)于 A => R 即輸入?yún)?shù)為逆變,返回結(jié)果為協(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的實(shí)參
// 輸入:A = Student, A- = Person
// 輸出:R = Person, R+ = Student
...
val arrStuFriends = friends(arrStudents, findStudent) 

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

對(duì)于某個(gè)對(duì)象消費(fèi)的值(the values an object consumes)適用逆變(-T),對(duì)于它產(chǎn)出的值(the values it produces)則適用協(xié)變(+T)。
如果一個(gè)對(duì)象同時(shí)消費(fèi)和產(chǎn)出某值,則類型應(yīng)該是不變(invariant)

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

// 錯(cuò)誤1:協(xié)變T出現(xiàn)在【replaceFirst所屬的類型(newFirst: T)Pair[T]】不變點(diǎn)
// 錯(cuò)誤2:協(xié)變T出現(xiàn)在【newFirst所屬的類型T】逆變點(diǎn)
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) {
    // 正確:引入一個(gè)新的類型R并且做類型界定,此時(shí)R是invariant,invariant出現(xiàn)在逆變點(diǎ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] // 錯(cuò)誤
解決方法:繼承List[Nothing],因?yàn)镹othin是所有類型的子類型

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也有對(duì)應(yīng)的通配符表示法
Java: void makeFriends(Pair<? extends Person> people) // 可以用List<Student>作為參數(shù)調(diào)用
Scala: def makeFriends(people: Pair[_ <: Person])
// 如果Pair是協(xié)變的,不需要用通配符表示;如果Pair是不變的,可以用通配符。

練習(xí)答案

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 隱式轉(zhuǎn)換 RichInt -> Comparable[Int], 即 Int 隱式轉(zhuǎn)換 Comparable[Int],
    只有實(shí)現(xiàn)Comparable[Int] 才方便使用視圖界定 T <% Comparable[T]。
    若實(shí)現(xiàn)的是Comparable[RichInt],則使用視圖界定時(shí)要求 T為RichInt,而整型字面量默認(rèn)為Int需要先轉(zhuǎn)換為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í)不懂隱式類型證明參數(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 就出錯(cuò),不懂
    def swap(implicit ev: T =:= S) = {
        val tmp = first.asInstanceOf[T]
        first = second
        second = tmp        
        this
    }
}

// 寫成外部函數(shù)就出錯(cuò),不懂
// 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.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容