本節翻譯自
綜述:在本節中,你將學會如何使用特質;以及抽象型別、自身型別和複合型別這幾個高階型別。
特質
特質用於在類之間共享介面和欄位。它們類似於Java 8的介面。類和物件可以擴充套件特徵,但是特質不能被例項化,因此沒有引數。
定義特質
一個最小的特質就是關鍵字 trait
和識別符號:
trait HairColor
作為泛型型別和抽象方法,特質顯得特別有用。
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}
擴充套件 trait Iterator[A]
需要型別 A
還有方法 hasNext
和 next
的實現。
使用特質
使用關鍵字 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
,它在建構函式中由 Cat
和 Dog
實現。在最後一行,我們呼叫 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中,這可以通過複合型別 的幫助來表示,這些型別是物件型別的交集。
假設我們有兩個特質 Cloneable
和 Resetable
:
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
同時是 Cloneable
和 Resetable
。這種複合型別在Scala中是這樣寫的:Cloneable with Resetable
。
這是更新的函式:
def cloneAndReset(obj: Cloneable with Resetable): Cloneable = {
//...
}
複合型別可以由幾個物件型別組成,它們可能有一個單獨的細化,可以用來縮小現有物件成員的簽名。一般的形式是:A with B with C ... { refinement }
在抽象型別中給出了細化的例子。