首页 > 代码库 > 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学习笔记