scala学习 - 特质

本文来自《Programming in Scala》一书

Scala学习之特质(trait)

1 特质的定义

特质的定义和类相似,只是使用trait关键字代替class,如下:

trait TraitA{
  def printA():Uint = {
    println("trait TraitA")
  }
}
  1. 定义了trait之后可以使用extends或者with把它混入类中,extends类似java的extends关键字,一个类不可以多次extends;with可以像implements一样定义多个。

  2. trait不能像class一样给主构造器提供参数

    class ClassA(name:String){...}   //这是合法的,可以提供主构造器参数
    trait TraitA(name:String){...}   //非法的
    
  3. 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

顺序化按照符合这些原则(我自己理解的):

  1. 拓扑排序,被依赖不会早于依赖它的,比如上买呢ExtendIntQueue一定是最早的,它不依赖任何类,ExtendIntQueue之后,TraitA,TraitB,TraitC将不被依赖。
  2. 从右到左,ExtendIntQueue之后,TraitA,TraitB,TraitC将不被依赖。但是从右到左一次是TraitB, TraitA, TraitC.
  3. 深度搜索,比如从右到左先解析TraitB之后,此时TraitB的父类型从右到左是TraitD,IntQueue. 此时会尝试TraitD,但是TraitD还被TraitA依赖, IntQueue也被依赖所以不行把TraitD加入到顺序里

用S来表示加入的顺序,D表示无下游依赖的类或特质,此时D初始值为(ExtendIntQueue),S为(),照以上规则:

  1. 首先加入ExtendIntQueue,它没有被依赖,此时TraitA,TraitB,TraitC不在被依赖。S为(ExtendIntQueue)
  2. 需要从TraitA,TraitB, TraitC中选择,选最右TraitB, S为(ExtendIntQueue, TraitB), D为(TraitA,TraitC). 判断TraitB的父类,如果有不再被依赖的比如TraitX,则加入S(这是一个递归往上的过程,如果TraitX上有TraitY, 还会判断是否要加入TraitY)
  3. 从D中选TraitA加入S,此时TraitA最右,S为(ExtendIntQueue, TraitB, TraitA), D为(TraitC, TraitD)。 像2中TraitB一样,需要判断TraitA的父类是否有不被依赖的类,TraitD不在被依赖,加入S, S为(ExtendIntQueue, TraitB, TraitA,TraitD)。
  4. 从D中选TraitC,S为(ExtendIntQueue, TraitB, TraitA,TraitD,TraitC), 此时IntQueue不在被依赖,加入D, D为(IntQueue).
  5. 从D中选IntQueue, 最终S为 (ExtendIntQueue, TraitB, TraitA,TraitD,TraitC, IntQueue)

总上顺序化后的继承体系类似:

ExtendIntQueue -> TraitB ->TraitA -> TraitD -> TraitC -> IntQueue

也就有了上面代码中的输出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 这篇讲义只讲scala的简单使用,目的是使各位新来的同事能够首先看懂程序,因为 scala 有的语法对于之前使用习...
    MrRobot阅读 2,900评论 0 10
  • 本章要点 类可以实现任意数量的特质 特质可以要求实现它们的类具备特定的字段、方法或超类 和Java接口不同,Sca...
    胡杨1015阅读 764评论 0 0
  • 高温来袭,请大家做好防暑准备。离开了空调房的舒适,该如何面对炎炎烈日?从穿衣打扮的角度来说,燥热流汗那是免不了的,...
    拍范阅读 394评论 1 1
  • 作为一名终极宅男屌丝,此次万科事件与本屌唯一的联系就是,如果我提早买了万科的股票,那我就赚翻了!事实是,我没买,...
    拾遗书生阅读 259评论 0 0
  • 在行&分答前些日公布,公司完成A+轮融资。本轮投资来自腾讯投资。今年6月份,公司已获得来自元璟资本、红杉资本中国基...
    纵横四海行天下阅读 158评论 0 0