特质(trait)是scala里代码服用的基础单元。特质封装了方法和字段的定义,并可以通过“混入”到类中重用它们。与类的继承时每个类都只能继承唯一的超类不同,类可以混入任意多个特质。特质的定义除了使用关键字trait之外,与类定义无异,如代码1-1

代码1-1

trait Bird {
  def fly = println("鸟飞翔")
  def singing
}

 这个特质名为Fish,它没有声明超类,因此和类一样,有个默认的超类AnyRef。它定义了一个具体方法fly,也定义了一个抽象方法singing,等待被混入的类实现。一旦特质被定义了,就可以使用extends或with关键字,把它混入到类中,scala的混入特质并不是继承它们,将在本文的后面说明。

模拟一个场景,一个机器人继承了人类,人会说话,而机器人想要继续继承鱼的游泳、和鸟的飞翔和唱歌,需要用到特质,如代码1-2,Rebot类最先开始继承Person类,接着混入Fish特质和Bird特质,因此,机器人除了能工作,还能模拟鸟飞翔

代码1-2

scala> class Person {
  def say = println("人说话")
}

trait Fish {
  def swim = println("鱼游泳")
}

trait Bird {
  def fly = println("鸟飞翔")
  def singing
}

class Rebot extends Person with Fish with Bird {
  def work = println("机器人工作")
  def singing = println("机器人唱歌")
}
defined class Person
defined trait Fish
defined trait Bird
defined class Rebot

scala> val rebot = new Rebot()
rebot: Rebot = Rebot@3e44f2a5

scala> rebot.work
机器人工作 scala> rebot.fly 鸟飞翔 scala> rebot.singing 机器人唱歌 scala> val fish: Fish = rebot fish: Fish = Rebot@3e44f2a5 scala> fish.swim 鱼游泳

 另外,因为Rebot这个类混入了Fish特质,所以可以用Fish的变量去接收Rebot对象,特质有点类似Java的抽象类,可以声明抽象方法和具体方法,但是Java的一个类只能继承一个抽象类,而scala一个类却可以混入多个特质,另外,特质不能带有任何类参数。代码1-3是合法的,而代码1-4则会报错

代码1-3

class Cat(color: String, age: Int)

 代码1-4

trait Cat(color: String, age: Int)

 如果想在特质里加入参数时,可如代码1-5这样做

代码1-5

scala> trait Cat {
  val color: String
  val age: Int
  def cry = println("小猫喵喵叫")
  def printColor = println("小猫的颜色是" + color)
  def printAge = println("小猫的年龄是" + age + "岁")
}
defined trait Cat

scala> val cat = new {
      val color = "白色"
      val age = 1
    } with Cat
cat: Cat = $anon$1@6bea52d4

scala> cat.printColor
小猫的颜色是白色

scala> cat.printAge
小猫的年龄是1岁

scala> cat.cry
小猫喵喵叫

 

Ordered特质

对象的比较是程序里常见的操作,比方定义一个有理数对象,用户可能调用<或者>=来判断两个对象之间的大小关系,如果没有Ordered特质之前,我们一般会像代码1-6这样做,Rational类是有理数类,类参数n代表分子,类参数d代表分母,私有成员g是n和d的最大公约数,我们会定义操作符来判断两个有理数对象的大小关系

代码1-6

scala> class Rational(n: Int, d: Int) {
  require(d != 0)
  private val g = gcd(n, d)
  val number = n / g
  val denom = d / g
  override def toString = number + "/" + denom
  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
  def <(that: Rational) = number * that.denom < that.number * denom
  def >(that: Rational) = that < this
  def ==(that: Rational) = (number == that.number && denom == that.denom)
  def <=(that: Rational) = this < that || this == that
  def >=(that: Rational) = this > that || this == that
}
defined class Rational

scala> val x = new Rational(1, 2)
x: Rational = 1/2

scala> val y = new Rational(1, 3)
y: Rational = 1/3

scala> x > y
res11: Boolean = true

 通过定义符号,我们可以比较对象间的大小关系,但scala专门提供了一个特质解决这样的问题,这个特质就是Ordered,Ordered特质让你仅仅只实现一个方法compare,使你的类可以使用>、<、>=、<=全套的比较方法,如代码1-7

代码1-7

scala> class Rational(n: Int, d: Int) extends Ordered[Rational] {
  require(d != 0)
  private val g = gcd(n, d)
  val number = n / g
  val denom = d / g
  override def toString = number + "/" + denom
  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
  def compare(that: Rational) = number * that.denom - that.number * denom
}
defined class Rational

scala> val a = new Rational(1, 3)
a: Rational = 1/3

scala> val b = new Rational(1, 5)
b: Rational = 1/5

scala> a > b
res12: Boolean = true

scala> val c = new Rational(1, 2)
c: Rational = 1/2

scala> val d = new Rational(2, 5)
d: Rational = 2/5

scala> c < d
res13: Boolean = false

scala> val e = new Rational(1, 3)
e: Rational = 1/3

scala> val f = new Rational(1, 5)
f: Rational = 1/5

scala> e >= f
res14: Boolean = true

 compare主要返回两个有理数对象做计算最后的结果,如果结果大于0则代表前一个对象大于后一个对象,如果结果小于0则代表前一个对象小于后一个对象,如果结果等于0代表两个对象相等

 

特质用来做可堆叠的改变

举个例子,思考一下对一个整数队列堆叠改动。队列有两种操作:put,把整数放入队列,和get,从队列取出整数,队列是先进先出的,因此get应该依整数进入队列时的顺序把它们取出来。

假设有一个类实现了这样的队列,你可以定义特质执行如下的改动:

  • Doubling:把所有放入到队列的数字加倍。
  • Incrementing:把所有放入到队列的数字增值
  • Filtering:从队列中过滤掉负数

这三种特质代表了改动,因为它们改变了原始队列的行为而并非定义了全新的队列。这三种同样也是可堆叠的。

代码1-8为抽象的IntQueue类,put方法把整数添加到队列中,get方法返回并移除开头的整数,length返回队列的长度

abstract class IntQueue {
  def get(): Int
  def put(x: Int)
  def length(): Int
}

 代码1-9的BasicIntQueue类是抽象类IntQueue的实现

代码1-9

class BasicIntQueue extends IntQueue {
  private val buf = new scala.collection.mutable.ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) = buf += x
  def length() = buf.length
}

代码1-10是运行时的样子:

 代码1-10

scala> val queue = new BasicIntQueue
queue: BasicIntQueue = BasicIntQueue@4fdfa676

scala> queue.put(-1)

scala> queue.put(2)

scala> queue.put(3)

scala> queue.length()
res3: Int = 3

scala> queue.get()
res4: Int = -1

scala> queue.get()
res5: Int = 2

scala> queue.get()
res6: Int = 3

 现在用特质改变它的行为,代码1-11展示了过滤掉负数,Filtering做了两件事情,第一件是它定义了超类IntQueue,这个定义意味着它只能混入扩展了IntQueue,因此可以把Filtering混入到BasicIntQueue,第二件事情是特质在声明为抽象的方法中有一个super的调用。这种调用对于普通的类来说是非法的,执行时必然失败。然而对于特质来说,则能调用成功。因为特质的super调用是动态绑定的。特质Filtering的super调用将直到被混入另一个特质或类之后,有了具体的方法定义时才工作。

这种安排对于实现可堆叠改动的特质来说是常常要用到的,为了告诉编译器你的目的,你必须在这种方法打上abstract override的标志。这种标识符的组合仅在特质成员的定义中被认可,在类中不行,它意味着特质碧玺混入某个具有期待方法的具体定义的类中。

代码1-11

trait Filtering extends IntQueue {
  abstract override def put(x: Int) = if (x >= 0) super.put(x)
}

 代码1-12中,queue变量只是简单地指明了一个类并混入一个特质,可以用这样的形式来替代命名类,联系代码1-11和代码1-12,我们将-1、2、3依次放入队列,但最后返回的队列长度只有2,因为-1被过滤掉了

代码1-12

scala> val queue = new BasicIntQueue with Filtering
queue: BasicIntQueue with Filtering = $anon$1@59221b97

scala> queue.put(-1)

scala> queue.put(2)

scala> queue.put(3)

scala> queue.length()
res3: Int = 2

scala> queue.get()
res4: Int = 2

scala> queue.get()
res5: Int = 3

 我们再加入两个特质,如代码1-13

代码1-13

trait Doubling extends IntQueue {
  abstract override def put(x: Int) = super.put(2 * x)
}

trait Incrementing extends IntQueue {
  abstract override def put(x: Int) = super.put(x + 1)
}

 代码1-14,我们可以看到,queue最长的长度为3,然而我们却放入了4个整数,两个负数两个正数,并且返回的数跟我们原先放入的数不同,越靠近右侧的特质越先起作用。Incrementing最先调用,对所有放入的数加1,接着就是Filtering,我们放入的-10+1后为-9,依旧小于0,所有我们放入的整数只有-1、2、3能到达Doubling,在到达Doubling,3个数经过前两个特质的转化后,变为0、3、4,经过Doubling后,每个数乘以2,为0、6、8,最后放入队列的就是0、6、8。

代码1-14

scala> val queue = new BasicIntQueue with Doubling with Filtering with Incrementing
queue: BasicIntQueue with Doubling with Filtering with Incrementing = $anon$1@131fcb6f

scala> queue.put(-10)

scala> queue.put(-1)

scala> queue.put(2)

scala> queue.put(3)

scala> queue.length()
res10: Int = 3

scala> queue.get()
res11: Int = 0

scala> queue.get()
res12: Int = 6

scala> queue.get()
res13: Int = 8

 特质是一种继承多个类似于类的结构的方式,但是它与许多语言中的多重继承有很重要的差别。其中的一个尤为重要:super的解释。对于多重继承来说,super调用导致的方法调用可以在调用发生的地方明确决定。而对于特质来说,方法调用是由类和被混入到类的特质的线性化决定的。

posted on 2017-05-01 11:51  百里琰  阅读(467)  评论(0编辑  收藏  举报