【Scala之旅】特质与高级类型

本节翻译自

综述:在本节中,你将学会如何使用特质;以及抽象类型、自身类型和复合类型这几个高级类型。

特质

特质用于在类之间共享接口和字段。它们类似于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 }

抽象类型中给出了细化的例子。

相关推荐