【Scala之旅】特质与高级类型
本节翻译自
综述:在本节中,你将学会如何使用特质;以及抽象类型、自身类型和复合类型这几个高级类型。
特质
特质用于在类之间共享接口和字段。它们类似于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 }
在抽象类型中给出了细化的例子。