Chapter11 類型參數

1. 泛型類

個人理解:泛型實質就是類或方法在定義時沒有給定具體參數類型,需要在編譯或運行時自動推斷出類型的一種方法。

  • 方括號來定義類型參數,放在類名後面。下面就定義了一個帶有兩個類型參數T和S的類。
    class Pair[T, S](val first: T, val second: S) {
      override def toString = "(" + first + "," + second + ")"
    }
  • 在類的定義中,可以用類型參數類定義變量、方法參數,以及返回值類型。

  • 帶有一個或多個類型參數的類是泛型的,Scala會從構造參數推斷出實際類型,也可以自己指定類型。

    val p = new Pair(42, "String")

    val p2 = new Pair[Any, Any](42, "String")

2. 泛型函數

  • 函數和方法也可以帶類型參數,類型參數放在函數名或方法名後面。
    def getMiddle[T](a: Array[T]) = a(a.length / 2)

3. 類型變量界定

個人理解:使用類型參數定義了某個參數或變量之後,要使用某種類型的的方法,比如使用String類型的compareTo方法,但我們可能傳進去的是一個Int類型,Int類型沒有該方法,這時程序就會出錯,下面就介紹如何解決這類問題。

  • 有時需要對類型變量進行限制,比如有個Pair類,該類中使用了compareTo方法,但我們並不知道first有這個方法。
    class Pair[T](val first: T, val second: T) {
      def smaller = if (first.compareTo(second) < 0) first else second // Error
    }
  • 解決方法,添加一個上界T <: Comparable[T]
    // 這裏要求T必須是Comparable[T]的子類型。
    class Pair[T <: Comparable[T]](val first: T, val second: T) {
      def smaller = if (first.compareTo(second) < 0) first else second
    }
  • 如果我們現在想要定義一個方法,用一個值替換上面定義Pair中的第一個值,如果Pair是不可變的,我們就需要返回新的Pair。這時候我們要引入下界來解決這個問題,下界用[R >: T]來表示。
    // 替換進來的類型必須是原類型的超類型
    // 比如現在有Pair[Student],要用Person來替換第一個值,結果是Pair[Person]。
    class Pair[T](val first: T, val second: T) {
      def replaceFirst[R >: T](newFirst: R) = new Pair(newFirst, second)
    }

4. 視圖界定

個人理解:即使上面使用上界約束了傳入的類型,我們在調用Pair(1,4)時,編譯器還是會報Int不是Comparable[Int]子類的錯誤,其實我們有一個RichInt中實現了該方法,Int到RichInt可以通過隱式轉換來完成。下面就是講解如何用視圖界定的方法解決該問題。

  • 視圖界定中使用<%關係表示T可以被隱式轉換成Comparable[T]。
    class Pair[T <% Comparable[T]](val first: T, val second: T) {
      def smaller = if (first.compareTo(second) < 0) first else second
    }

    // Ordered提供關係操作符
    class Pair[T <% Ordered[T]](val first: T, val second: T) {
      def smaller = if (first < second) first else second
    }
  • 隱式轉換將在後面一個Chapter中講解,這裏只要知道有這個概念就行。

5. 上下文界定

個人理解:其實上面視圖界定還有一個問題,當你使用視圖界定前必須知道有從T到V的隱式轉換存在,要是沒有呢,我們該怎麼辦?下面就介紹該小結內容”上下文界定“,它是如何解決次問題的。

  • 上下文界定的形式是:T:M,M是另一個泛型類,它要求必須存在一個類型爲M[T]的隱式值,這裏只要知道隱式值是用implicit定義就可以了,下節會詳細講解。
    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
    }

6. Manifest上下文界定

個人理解:構造一個泛型數組,在Scala中需要我們將上下文界定的M指定爲Manifest,這節其實是上下文界定的一個實際應用場景。

  • 實例化一個泛型的Array[T]或者說是構造一個泛型數組,我們需要一個Manifest[T]對象。Manifest就是一個隱式參數,這裏可以用上下文界定。
    def makePair[T : Manifest](first: T, second: T) = {
      val r = new Array[T](2); r(0) = first; r(1) = second; r
    }

7. 多重界定

  • 類型變量可以同時有上界和下界:T >: Lower <: Upper

  • 不能同時有多個上界或多個下界,一個類型可以實現多個特質。

    T <: Comparable[T] with Serializable with Cloneable
  • 可以有多個視圖界定:T <% Comparable[T] <% String

  • 可以有多個上下文界定:T : Ordering : Manifest


8. 類型約束

個人理解:該小節提出類型約束,主要是要掌握它的兩種用途。

  • 類型約束有如下三種方式:
    T =:= U // 測試T是否等於U
    T <:< U // 測試T是否是U的子類
    T <%< U // 測試T是否被隱式轉換爲U
  • 使用約束,需要添加隱式類型證明參數
      class Pair[T](val first: T, val second: T)(implicit ev: T <:< Comparable[T]) = // 使用約束
  • 上面的類型約束的使用並沒有比類型界定帶來更多優點,類型約束主要用在以下兩種場景。

  • 類型約束讓你可以在泛型類中定義只能在特定條件下使用的方法

    // 即使File不是帶先後順序的,這裏還是可以構造出Pair[File]
    // 只是當你調用smaller方法時,纔會報錯
    class Pair[T](val first: T, val second: T) {
      def smaller(implicit ev: T <:< Ordered[T]) = // 使用約束
        if (first < second) first else second
    }
  • 類型約束的另一個用途是改進類型推斷
    // 下面調用第二條語句會得到推斷的類型參數是[Nothing, List[Int]]不符合[A, C <: Iterable[A]]
    // 因爲單憑List(1, 2, 3)是無法推斷出A是什麼,因爲它們是在同一個步驟中匹配A和C的。

    def firstLast[A, C <: Iterable[A]](it: C) = (it.head, it.last)
    firstLast(List(1, 2, 3)) // 出錯
  • 解決辦法是,先匹配C,在匹配A。
    def firstLast[A, C](it: C)(implicit ev: C <:< Iterable[A]) =
      (it.head, it.last)
    firstLast(List(1, 2, 3)) // 成功

9. 型變

個人理解:如果只是看書本,這小結還是挺不容易看明白的。其實,仔細分析下,協變和逆變還是很好理解的。如果將父類作爲函數參數,想子類作爲參數也可以調用,這就是協變;如果將子類作爲函數參數,想父類作爲參數也可以調用,這就是逆變。這段話如果沒看明白,先看下面具體例子,看完後再回來看這段話。

  • 假設有函數定義def makeFriends(p: Pair[Person]),並且Student是Person的子類,如果使用Pair[Student]作爲參數去調用makeFriends函數,程序會報錯。要想實現這種方式的調用,需要把Pair定義修改如下:
    class Pair[+T](val first: T, val second: T)
  • +T(協變)表示某個泛型類的子類型關係和參數T方向一致。

  • 下面講解第二種型變方式逆變

    trait Friend[-T] { // -T表示逆變
      def befriend(someone: T)
    }

    class Person(name: String) extends Friend[Person] {
      def befriend(someone: Person) {
          this + " and " + someone + " are now friends."
      }
    }

    // Student是Person子類
    class Student(name: String) extends Person(name)

    // 需要注意該函數定義的傳入參數是子類類型
    def makeFriendWith(s: Student, f: Friend[Student]) { f.befriend(s) }

    val susan = new Student("Susan")
    val fred = new Person("Fred")
    // 可以調用
    makeFriendWith(susan, fred) 
  • -T(逆變)表示某個泛型類的父類型關係和參數T方向一致,剛好與協變相反。

10. 協變和逆變點

  • 函數在參數上是逆變的,返回值上是協變的。逆變適應於表示輸入類型參數協變適應於表示輸出類型參數

  • Scala中數組類型是不支持型變的,因爲它作爲輸入和輸出類型要保持不變。

  • 參數的位置是逆變點,返回值類型的位置是協變點,在函數的參數中,型變是反過來的,它的參數是協變的。

    foldLeft[B](z: B)(op: (B, A) => B): B
                   -       +  +     -   +

11. 對象不能泛型

  • 不能將參數化類型添加到對象。
    abstract class List[+T] {
      def isEmpty: Boolean
      def head: T
      def tail: List[T]
    }

    // 對象不能寫成 object Empty[T] extends List[T] 
    // 類可以使用 class Empty[T] extends List[T]     
    // 這裏可以使用Nothing,前面說過Nothing是所有類型的子類型。
    object Empty extends List[Nothing] {
      def isEmpty = true
      def head = throw new UnsupportedOperationException
      def tail = throw new UnsupportedOperationException
    }

12. 類型通配符

  • Scala中類型可以使用通配符。
    def process(people: java.util.List[_ <: Person]

【待續】

發佈了84 篇原創文章 · 獲贊 324 · 訪問量 66萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章