首页 > 代码库 > scala中trait学习笔记

scala中trait学习笔记

scala中提供的trait(特质)和Java中的Interface有很多相似之处。都可以持有方法的声明和属性,但是trait还有比interface强大的多的其他用法。

1. trait可以带有方法实现;

2. trait与interface一样,可以互相继承。但是trait可以继承自某个类,但是这种特质只能够混入父类的子类中,不能随意混入;

3. trait中可以在运行时动态调用方法。

下面举一个trait使用的例子。

首先定义一个虚类IntQueue和特质Logger

abstract class IntQueue {

  def put(n: Int): Unit

  def get(): Int
}
trait Logger {
  def log(n: Int, action: String)
}

可以看到put、get与log方法,到目前为止都只有声明。接着我们定义一个IntQueue的子类,来实现这三个方法。

class BasicIntQueue extends IntQueue with Logger {

  private val buffer = new ArrayBuffer[Int]

  def put(n: Int): Unit = {
    buffer += n
    log(n, "add")
  }

  def get(): Int =  buffer.remove(0)

  def log(n: Int, action: String): Unit = println("In BasicIntQueue: " + action + " " + n)
}

可以看到上面三个方法都被实现,要注意这3个方法都没有加override修饰符。这是因为他们都是方法声明的实现,并没有重写。当然这里加上override也不会报错。

同时,我们为了测试trait的能力,也在一个trait中,实现put方法。

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

在Doubling特质中,调用了父类的put方法,只是放入队列中的值被翻倍。

注意,这里对父类的方法调用,在编译和运行时,其实是不同的。

在编译阶段,Doubling特质只知道这个方法继承自IntQueue虚类,因此这里的super.put是对IntQueue中的方法声明的调用,找不到具体的实现。因此这里在put方法前,必须加上abstract关键字,同时override也不可省略!(不省略override的原因,个人理解是因为这个方法也不能称之为完整的实现,因此也是对声明的“重写”)

现在我们要开始使用BasicIntQueue与Doubling了

  def main(args: Array[String]): Unit = {
    val queue = new BasicIntQueue with Doubling
    queue.put(1)
    queue.put(2)
    queue.put(-1)
    queue.put(0)

    println(queue.get())
    println(queue.get())
    println(queue.get())
    println(queue.get())
  }

这时我们可以得到结果:

In BasicIntQueue: add 2
In BasicIntQueue: add 4
In BasicIntQueue: add -2
In BasicIntQueue: add 0
2
4
-2
0

可以发现,在混入了Doubling特质之后,放入队列中的值都被翻倍了。

我们在定义queue对象时,使用的写法是new BasicIntQueue with Doubling,这其中隐含一条scala的规则,同名的方法都是由最右侧的实现开始生效,并且在右侧的方法实现中,对super的调用,会调用到左侧的方法实现。

这里queue对put方法的调用应该是首先调用了Doubling中的实现,接着Doubling的实现中带有super.put,这一动作调用了BasicIntQueue中的方法。这一调用是运行中动态指定的。从这个角度来看,其实在main方法写好之后,Doubling中的super.put,其实已经知道了调用的指向。只是目前scala没有能够在编译时做到这么全面的代码查找,否则put方法前的abstract修饰符是可以去掉的,不过scala的编译已经不快了,而且做这些工作价值也不大。

通过这个例子可以发现scala中的trait可以用来把某些关键的逻辑剥离,实现对逻辑部分最大的复用。

scala中trait学习笔记