本文来自《Programming in Scala》一书
Scala学习之特质(trait)
1 特质的定义
特质的定义和类相似,只是使用trait
关键字代替class
,如下:
trait TraitA{
def printA():Uint = {
println("trait TraitA")
}
}
定义了trait之后可以使用extends或者with把它混入类中,extends类似java的extends关键字,一个类不可以多次extends;with可以像implements一样定义多个。
-
trait不能像class一样给主构造器提供参数
class ClassA(name:String){...} //这是合法的,可以提供主构造器参数 trait TraitA(name:String){...} //非法的
trait不想java的interface,trait可以实现方法,有点类似
abstract class
。
2 可堆叠改变
一个类混合了多个特质,这些特质的方法可以堆叠在一起共同决定类的方法所表现出来的行为。
想要使用trait的可堆叠功能,trait的方法里必须使用到super,像下面这样:
class IntQueue {
private val buf = new ArrayBuffer[Int]()
def put(i : Int) = {
println("In IntQueue")
buf.append(i)
}
def get():Int={
println("In IntQueue")
buf.remove(0)
}
}
-----------------------------------------------------------
trait TraitC extends IntQueue{
override def put(i: Int): Unit = {
println("In TraitC...")
super.put(i)
}
override def get(): Int = super.get()
}
traitC 继承IntQueue,并在put
方法中调用super.put
,这并不代表就是调用IntQueue#put方法,super.put
方法是动态的决定的,根据混入特质TraitC的类中TraitC所处位置有关,比如下面的代码:
class IntQueue {
private val buf = new ArrayBuffer[Int]()
def put(i : Int) = {
println("In IntQueue")
buf.append(i)
}
def get():Int={
println("In IntQueue")
buf.remove(0)
}
}
----------------------------------------------------------
trait TraitA extends IntQueue {
override def put(i: Int): Unit = {
println("In TraitA...")
super.put(i)
}
override def get(): Int = {
println("In TraitA...")
super.get
}
}
----------------------------------------------------------
trait TraitB extends IntQueue{
override def put(i: Int): Unit = {
println("In TraitB...")
super.put(i)
}
override def get(): Int = {
println("In TraitB...")
super.get()
}
}
----------------------------------------------------------
object TestTrait {
class ExtendIntQueue extends IntQueue with TraitA with TraitB{
override def put(i: Int): Unit = {
println("In ExtendIntQueue...")
super.put(i)
}
override def get(): Int = {
println("In ExtendIntQueue...")
super.get()
}
}
def main(args : Array[String]): Unit ={
val etq = new ExtendIntQueue
etq.put(0)
}
}
继承体系:
----> InitQueue
| ^
| |
| ----------
| | |
| TraitA TraitB
| ^ ^
| | |
| ----------
| |
|------ ExtendIntQueue
上面代码最终输出:
In ExtendIntQueue...
In TraitB...
In TraitA...
In IntQueue
从上面代码的输出可以看出调用
ExtendIntQueue#put -> TraitB#put -> TraitA#put -> IntQueue#put
TraitB的super.put
被动态绑定成了TraitA, TraitA的super.put
调用了IntQueue#put. 上面代码的继承结构比较复杂,scala是怎么动态决定super的绑定?有一条基本的原则首先:同一个类混入的特质里,右边的先于左边的,比如class ExtendIntQueue extends IntQueue with TraitA with TraitB
,最右TraitB 先调用,它的super被绑定成TraitA。 上面这个过程被称为线性化,类ExtendIntQueue所有的超类被线性化,线性化后的继承体系就像ExtendIntQueue ->TraitB -> TraitA -> IntQueue
,因此put的调用变成了ExtendIntQueue#put ->TraitB#put -> TraitA#put -> IntQueue#put
scala是怎么做线性化的?下面是一个更加复杂的例子:
class IntQueue {
private val buf = new ArrayBuffer[Int]()
def put(i : Int) = {
println("In IntQueue")
buf.append(i)
}
}
----------------------------------------------------------
trait TraitA extends IntQueue with TraitD{
override def put(i: Int): Unit = {
println("In TraitA...")
super.put(i)
}
}
----------------------------------------------------------
trait TraitB extends IntQueue with TraitD{
override def put(i: Int): Unit = {
println("In TraitB...")
super.put(i)
}
}
----------------------------------------------------------
trait TraitC extends IntQueue{
override def put(i: Int): Unit = {
println("In TraitC...")
super.put(i)
}
}
----------------------------------------------------------
trait TraitD extends IntQueue{
override def put(i: Int): Unit = {
println("In TraitD...")
super.put(i)
}
}
----------------------------------------------------------
object TestTrait {
class ExtendIntQueue extends IntQueue with TraitC with TraitA with TraitB{
override def put(i: Int): Unit = {
println("In ExtendIntQueue...")
super.put(i)
}
}
def main(args : Array[String]): Unit ={
val etq = new ExtendIntQueue
etq.put(0)
}
}
----------------------------------------------------------
继承体系如下:
|--> IntQueue <--------------------|
| ^ <---- TraitD <----| |
| | ^ | |
| | | | |
| |------ TraitA TraitB ---|
| | ^ ^
| | | |
| TraitC ----------
| ^ ^
| | |
|---------------- ExtendIntQueue
上面代码打印输出是:
In ExtendIntQueue...
In TraitB...
In TraitA...
In TraitD...
In TraitC...
In IntQueue
顺序化按照符合这些原则(我自己理解的):
- 拓扑排序,被依赖不会早于依赖它的,比如上买呢ExtendIntQueue一定是最早的,它不依赖任何类,ExtendIntQueue之后,TraitA,TraitB,TraitC将不被依赖。
- 从右到左,ExtendIntQueue之后,TraitA,TraitB,TraitC将不被依赖。但是从右到左一次是TraitB, TraitA, TraitC.
- 深度搜索,比如从右到左先解析TraitB之后,此时TraitB的父类型从右到左是TraitD,IntQueue. 此时会尝试TraitD,但是TraitD还被TraitA依赖, IntQueue也被依赖所以不行把TraitD加入到顺序里
用S来表示加入的顺序,D表示无下游依赖的类或特质,此时D初始值为(ExtendIntQueue),S为(),照以上规则:
- 首先加入ExtendIntQueue,它没有被依赖,此时TraitA,TraitB,TraitC不在被依赖。S为(ExtendIntQueue)
- 需要从TraitA,TraitB, TraitC中选择,选最右TraitB, S为(ExtendIntQueue, TraitB), D为(TraitA,TraitC). 判断TraitB的父类,如果有不再被依赖的比如TraitX,则加入S(这是一个递归往上的过程,如果TraitX上有TraitY, 还会判断是否要加入TraitY)
- 从D中选TraitA加入S,此时TraitA最右,S为(ExtendIntQueue, TraitB, TraitA), D为(TraitC, TraitD)。 像2中TraitB一样,需要判断TraitA的父类是否有不被依赖的类,TraitD不在被依赖,加入S, S为(ExtendIntQueue, TraitB, TraitA,TraitD)。
- 从D中选TraitC,S为(ExtendIntQueue, TraitB, TraitA,TraitD,TraitC), 此时IntQueue不在被依赖,加入D, D为(IntQueue).
- 从D中选IntQueue, 最终S为 (ExtendIntQueue, TraitB, TraitA,TraitD,TraitC, IntQueue)
总上顺序化后的继承体系类似:
ExtendIntQueue -> TraitB ->TraitA -> TraitD -> TraitC -> IntQueue
也就有了上面代码中的输出。