【Scala之旅】特質與高階型別

gcusky發表於2019-02-16

本節翻譯自

綜述:在本節中,你將學會如何使用特質;以及抽象型別、自身型別和複合型別這幾個高階型別。

特質

特質用於在類之間共享介面和欄位。它們類似於Java 8的介面。類和物件可以擴充套件特徵,但是特質不能被例項化,因此沒有引數。

定義特質

一個最小的特質就是關鍵字 trait 和識別符號:

trait HairColor

作為泛型型別和抽象方法,特質顯得特別有用。

trait Iterator[A] {
  def hasNext: Boolean
  def next(): A
}

擴充套件 trait Iterator[A] 需要型別 A 還有方法 hasNextnext 的實現。

使用特質

使用關鍵字 extends 可以擴充套件特質。然後使用關鍵字 override 來實現該特質裡的任何抽象成員。

class IntIterator(to: Int) extends Iterator[Int] {
  private var current = 0
  override def hasNext: Boolean = current < to
  override def next(): Int =  {
    if (hasNext) {
      val t = current
      current += 1
      t
    } else 0
  }
}

val iterator = new IntIterator(10)
iterator.next()  // prints 0
iterator.next()  // prints 1

這個 IntIterator 類將一個引數 to 作為一個上界。它擴充套件了 Iterator[Int],這意味著方法 next 必須返回一個 Int。

子型別

可以在需要特徵的地方使用子型別。

import scala.collection.mutable.ArrayBuffer

trait Pet {
  val name: String
}

class Cat(val name: String) extends Pet
class Dog(val name: String) extends Pet

val dog = new Dog("Harry")
val cat = new Cat("Sally")

val animals = ArrayBuffer.empty[Pet]
animals.append(dog)
animals.append(cat)
animals.foreach(pet => println(pet.name))  // Prints Harry Sally

trait Pet 有一個抽象的欄位 name,它在建構函式中由 CatDog 實現。在最後一行,我們呼叫 pet.name,該名稱必須在特質 Pet 的任何子型別中實現。

抽象型別

特質和抽象類可以有一個抽象型別的成員。這意味著具體的實現定義了實際的型別。這裡有一個例子:

trait Buffer {
  type T
  val element: T
}

這裡我們定義了一個抽象型別 type T。它被用來描述 element。我們可以在抽象類中擴充套件這個特性,並向 T 新增一個上型別邊界繫結,以使其更具體。

abstract class SeqBuffer extends Buffer {
  type U
  type T <: Seq[U]
  def length = element.length
}

請注意我們是如何使用另一個抽象型別 type U 作為一個上型別繫結:這個 SeqBuffer 類允許我們只在 Buffer 中儲存序列,它宣告 type T 必須是 Seq U 的子型別,用於新的抽象型別 U

帶有抽象型別成員的特質或經常與匿名類例項化相結合使用。為了說明這一點,我們現在來看一個程式,它處理一個 SeqBuffer,引用了一個 Int 列表:

abstract class IntSeqBuffer extends SeqBuffer {
  type U = Int
}


def newIntSeqBuf(elem1: Int, elem2: Int): IntSeqBuffer =
  new IntSeqBuffer {
       type T = List[U]
       val element = List(elem1, elem2)
     }
val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

這裡有一個使用匿名類 IntSeqBuf 實現的工廠 newIntSeqBuf(即 new IntSeqBuffer),它將 type T 設定為一個 List[Int]

也可以將抽象型別成員轉換為類的型別引數,反之亦然。下面是上述程式碼的一個版本,它只使用型別引數:

abstract class Buffer[+T] {
  val element: T
}
abstract class SeqBuffer[U, +T <: Seq[U]] extends Buffer[T] {
  def length = element.length
}

def newIntSeqBuf(e1: Int, e2: Int): SeqBuffer[Int, Seq[Int]] =
  new SeqBuffer[Int, List[Int]] {
    val element = List(e1, e2)
  }

val buf = newIntSeqBuf(7, 8)
println("length = " + buf.length)
println("content = " + buf.element)

注意,我們必須在這裡使用型變註解(+T <: Seq[U]),以隱藏從方法 newIntSeqBuf 返回的物件的具體序列實現型別。此外,有些情況下,用型別引數代替抽象型別是不可能的。

自身型別

自身型別是一種宣告一種特質必須被混入到另一種特質的方式,儘管它並沒有直接地擴充套件它。這使得依賴項的成員可以不用匯入。

自身型別是小寫 this 或別名 this 的另一個識別符號的型別的一種方式。語法看起來像正常的函式語法,但表示完全不同的語法。

要在一個特質中使用自我型別,編寫一個識別符號,將另一個特質混入在一起,以及一個 =>(例如 someIdentifier: SomeOtherTrait =>)。

trait User {
  def username: String
}

trait Tweeter {
  this: User =>  // reassign this
  def tweet(tweetText: String) = println(s"$username: $tweetText")
}

class VerifiedTweeter(val username_ : String) extends Tweeter with User {  // We mixin User because Tweeter required it
  def username = s"real $username_"
}

val realBeyoncé = new VerifiedTweeter("Beyoncé")
realBeyoncé.tweet("Just spilled my glass of lemonade")  // prints "real Beyoncé: Just spilled my glass of lemonade"

因為我們說 this: User =>trait Tweeter,現在變數 username 適用於 tweet 方法。這也意味著 VerofoedTweeter 擴充套件了 Tweeter,它還必須混入 User(使用 with User)。

複合型別

有時,有必要表示物件的型別是其他型別的子型別。在Scala中,這可以通過複合型別 的幫助來表示,這些型別是物件型別的交集。

假設我們有兩個特質 CloneableResetable

trait Cloneable extends java.lang.Cloneable {
  override def clone(): Cloneable = {
    super.clone().asInstanceOf[Cloneable]
  }
}
trait Resetable {
  def reset: Unit
}

現在假設我們要寫一個函式 cloneAndReset,它取出一個物件,克隆它並重新設定原始物件:

def cloneAndReset(obj: ?): Cloneable = {
  val cloned = obj.clone()
  obj.reset
  cloned
}

問題是,引數 obj 的型別是什麼。如果它是 Cloneable 則物件可以被克隆但不可以被重置。如果它是 Resetable 我們可以重置物件但卻無法進行克隆操作。為了避免這種情況下的型別轉換,我們可以指定型別 obj 同時是 CloneableResetable。這種複合型別在Scala中是這樣寫的:Cloneable with Resetable

這是更新的函式:

def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
  //...
}

複合型別可以由幾個物件型別組成,它們可能有一個單獨的細化,可以用來縮小現有物件成員的簽名。一般的形式是:A with B with C ... { refinement }

抽象型別中給出了細化的例子。

相關文章