10、trait的动态混入 及 trait的叠加

1、问题引入

/**
  * Created by dwh on 2019/12/20.
  */
object MyTraitMixIn {
  def main(args: Array[String]): Unit = {
    val person1 = new Person() // 定义一个Person对象
    person1.name = "tom"
    person1.sayHello()
    person1.sayHi()

    val student1 = new Student() // 定义一个Student对象
    student1.name = "jerry"
    student1.sayHello()
    student1.sayHi()
    student1.doHomeWork()
  }
}

// 1、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait {
  def sayHello()

  def sayHi(): Unit = {
    println("Hi~~")
  }
}

// 2、定义一个类Person, 继承了一个trait, 那么就继承了它的所有函数
class Person() extends MyTrait {
  var name: String = _

  override def sayHello(): Unit = {
    println("Hello," + this.name)
  }
}

// 3、定义一个类Student, 继承了Person类
class Student extends Person {
  def doHomeWork(): Unit = {
    println("do homework....")
  }
}

问题引入:

  • 可以看到上面的代码,Person类从MyTrait继承的函数,也被传递给了Person的子类!
  • 目的:想让Person类拥有MyTrait函数,而不想让Person的子类也拥有MyTrait的函数!
  • 思考:直接在Person类定义的时候就extends MyTrait 显然是做不到的,那么就只能在定义Person对象是做一些手脚了。
  • 解决方案:动态混入,在不影响原有的继承关系的基础上,给制定的类扩展功能。

2、混入1个trait

  • 将上面的代码修改一下:
/**
  * Created by dwh on 2019/12/20.
  */
object MyTraitMixIn {
  def main(args: Array[String]): Unit = {
    val person1 = new Person // 定义一个Person对象
    person1.name = "tom"
    // person1.sayHello() //
    // person1.sayHi() // 以上这两句代码会报错,因为Person类没有继承MyTrait, 也就没有了这两个函数了

    val student1 = new Student // 定义一个Student对象
    student1.name = "jerry"
    // student1.sayHello()
    // student1.sayHi() // 以上这两句也会报错, 因为Person类没有继承MyTrait, 也就没有了这两个函数了, 那么Student作为他的子类, 当然也就没有这两个函数了
    student1.doHomeWork()

    // 重点来了: 给Person对象动态混入MyTrait的函数
    val person2 = new Person with MyTrait {
      override def sayHello(): Unit = { // 这里动态混入了trait, 那么势必要实现trait里面的抽象函数, 有点儿类似于java的匿名内部类
        println("Hello," + this.name)
      }
    }
    person2.name = "spark"
    person2.sayHi() // 这个会调用从MyTrait继承来的函数
    person2.sayHello() // 这个是自己从MyTrait继承来又自己实现了的函数
  }
}

// 1、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait {
  def sayHello()

  def sayHi(): Unit = {
    println("Hi~~")
  }
}

// 2、定义一个类Person
class Person() {
  var name: String = _
}

// 3、定义一个类Student, 继承了Person类
class Student extends Person {
  def doHomeWork(): Unit = {
    println("do homework....")
  }
}

从上面代码可以看出,可以只给某一个对象实例混入trait。

3、混入n个特质(n个特质之间没有关联)

/**
  * Created by dwh on 2019/12/20.
  */
object MyTraitMixIn {
  def main(args: Array[String]): Unit = {
    // 给Person对象动态混入MyTrait, MyTrait1, MyTrait2
    val person2 = new Person with MyTrait with MyTrait1 with MyTrait2 {
      override def sayHello(): Unit = { //
        println("Hello," + this.name)
      }
    }
    person2.name = "spark"
    person2.sayHi() // 这个会调用从MyTrait继承来的函数
    person2.sayHi1()
    person2.sayHi2()
    person2.sayHello() // 这个是自己从MyTrait继承来又自己实现了的函数
  }
}

// 1、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait {
  def sayHello()

  def sayHi(): Unit = {
    println("Hi~~")
  }
}

// 1、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait1 {
  def sayHello()

  def sayHi1(): Unit = {
    println("Hi1~~")
  }
}

// 1、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait2 {
  def sayHello()

  def sayHi2(): Unit = {
    println("Hi2~~")
  }
}

// 2、定义一个类Person
class Person() {
  var name: String = _
}

// 3、定义一个类Student, 继承了Person类
class Student extends Person {
  def doHomeWork(): Unit = {
    println("do homework....")
  }
}

从上面代码可以看出,可以给对象实例混入多个trait。
ps:当混入多个trait时,多个trait之间可以有相同的抽象方法,但是如果多个tarit之间有相同的非抽象方法,那么此时会报错误:anonymous class $anon inherits conflicting members

4、混入n个特质(n个特质之间有继承关系)

  • 高能预警, 下面的代码有可能引起不适,请谨慎观看:
package cn.tujia.bigdata

/**
  * Created by dwh on 2019/12/20.
  */
object MyTraitMixIn {
  def main(args: Array[String]): Unit = {
    // 给Person对象动态混入MyTrait4, MyTrait3, MyTrait2, MyTrait1, MyTrait
    // 0、首先开始混入第1个trait: 即MyTrait4, 此时代码走到了MyTrait4, 直接打印"MyTrait4", 至此第1个trait混入完毕。
    // 1、接着开始混入第2个trait: 即MyTrait3, 此时代码走到了MyTrait3, 发现MyTrait3还继承了MyTrait, 而MyTrait已经是最顶层了,
    // 所以此时先打印"MyTrait", 再打印"MyTrait3", 至此第2个trait混入完毕。
    // 2、然后开始混入第3个trait: 即MyTrait2, 此时代码走到了MyTrait2, 发现MyTrait2还继承了MyTrait1, 紧接着发现MyTrait1继承了MyTrait, 但是呢, MyTrait刚刚已经混入过了, 所以MyTrait就不会再次混入了,
    // 于是就打印了"MyTrait1", 再打印"MyTrait2", 至此第3个trait混入完毕。
    // 3、再然后准备混入第4个trait, 即MyTrait1, 发现已经混入过了, 不再混入
    // 4、最后准备混入第5个trait, 即MyTrait, 发现也已经混入过了,不再混入, 至此所以trait混入完毕!
    // 5、总结:
    //    混入时要按照从左到右的顺序依次混入:
    //    首先混入第1个trait, 如果这个trait有父trait, 那么会一直往上找, 直到把它所有的父类都混入
    //    接着混入第2个trait, 同样如果这个trait也有父类, 那么也会一直往上找, 知道把它所有的父类都混入,
    //    然后混入第3个trait, 第4个trait, ... 一直到第n个trait
    //    需要注意的是:trait只会被混入一次, 即类只会被jvm加载一次!
    val person2 = new Person with MyTrait4 with MyTrait3 with MyTrait2 with MyTrait1 with MyTrait {
      override def sayHello(): Unit = {
        println("Hello," + this.name)
      }
    }
    person2.name = "spark"
    // 函数的执行顺序和混入顺序刚好相反, 混入是从左到右依次混入, 而函数的执行顺序是从右到左依次执行:
    // 1、调用sayHi函数, 它会先去找从右边数第一个trait,即MyTrait, 然后随即发现他有个子类是MyTrait1, 紧接着又发现MyTrait1又有子类MyTrait2, 而MyTrait2已经没有子类了
    // 所以会首先调用了MyTrait2的sayHi函数, 马上就打印出"Hi2~~"
    // 2、打印出"Hi2~~"后, MyTrait2的sayHi函数还未执行完, 这个函数还有一句super.sayHi, 所以紧接着就执行super.sayHi这句话:
    // 3、要注意的是super.sayHi并不是指MyTrait2的父类中的函数, 而是指混入顺序在MyTrait2前的那个trait, 即MyTrait3
    // 4、于是就开始调用MyTrait3的sayHi函数, 于是就打印了"Hi3~~"
    // 5、然后在MyTrait3的sayHi函数也有一句super.sayHi, 那么此时应该调用混入顺序在MyTrait3前的那个trait了, 但是发现它前头已经没有trait了, 那么此时的super就是指真正的父类了,即MyTrait1,
    // 6、所以紧接着就打印了"Hi1~~"
    // 7、然后就又从MyTrait1.sayHi函数中的super.sayHi, 调用了父类MyTrait, 于是接着打印出了"Hi~~"
    // 8、因为函数是一层一层调用进来的, 调用完还要一层一层再退回去, 所以MyTrait的sayHi执行完, 就代表着MyTrait1的sayHi函数中的super.sayHi执行完了, 然后紧接着执行打印"Hi11~~"
    // 9、同理, 再往上层调用处返回, 就是MyTrait3的sayHi函数中的super.sayHi执行完了, 于是就打印了"Hi33~~"
    // 10、再往回返, 就返到了MyTrait2的sayHi函数中的super.sayHi函数, 所以就又打印出了"Hi22~~"
    // 11、此时调用结束。 (ps: 可以看出, person2对象调用了sayHi函数, 实际调用的是MyTrait2的sayHi函数, 从打印Hi2~~开始, 中间有各种深层次的调用, 最终又逐层返回到Hi22~~, 这个跟java的方法栈是一模一样的)
    // 总结:
    // 1、混入函数调用时, 是从右往左调用:
    // 2、如果右边的第一个trait有子类, 那么他本身的函数就不会被调用了, 子类的函数会被调用, 如果子类还有子类, 那么依次深入, 直到最底层的那个子类的函数!
    // 3、最底层的函数执行调用时, 它的函数体内可能又调用了别的函数, 然后一层一层调用, 最后再一层一层滴返回来, 直到把那个子类的函数体的语句全部运行完毕!
    // 4、如果函数体内都是正常的语句, 那么这个函数执行完了就紧接着该执行他前头的那个trait的函数了,
    // 5、但如果函数体内是不正常的语句, 即有super.xxx这样的语句, 需要注意的是, 这里的super指的可不是这个子类的父类, 而是混入顺序在他前头的那个trait
    person2.sayHi()

    person2.sayHi4() // ps: 如果trait和其他的trait没有继承关系, 那么直接正常调用即可 !
    person2.sayHello()
  }
}

// 1、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait {
  println("MyTrait")

  def sayHello()

  def sayHi(): Unit = {
    println("Hi~~")
  }
}

// 2、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait1 extends MyTrait {
  println("MyTrait1")

  def sayHello()

  override def sayHi(): Unit = {
    println("Hi1~~")
    super.sayHi()
    println("Hi11~~")
  }
}

// 3、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait2 extends MyTrait1 {
  println("MyTrait2")

  def sayHello()

  override def sayHi(): Unit = {
    println("Hi2~~")
    super.sayHi()
    println("Hi22~~")
  }
}

// 4、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait3 extends MyTrait {
  println("MyTrait3")

  def sayHello()

  override def sayHi(): Unit = {
    println("Hi3~~")
    super.sayHi()
    println("Hi33~~")
  }
}

// 4、定义一个trait, 里面有一个抽象函数和一个非抽象函数
trait MyTrait4 {
  println("MyTrait4")

  def sayHello()

  def sayHi4(): Unit = {
    println("Hi4~~")
  }
}

// 5、定义一个类Person
class Person() {
  var name: String = _
}

// 6、定义一个类Student, 继承了Person类
class Student extends Person {
  def doHomeWork(): Unit = {
    println("do homework....")
  }
}

一个注意点:

如果trait中既有抽象函数又有非抽象函数,那么这个trait也称为富接口。
如果trait中有非抽象字段,那么当某个实例混入该trait,这个字段是直接加入到实例中的,而并非是继承来的!

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,101评论 1 32
  • 这篇讲义只讲scala的简单使用,目的是使各位新来的同事能够首先看懂程序,因为 scala 有的语法对于之前使用习...
    MrRobot阅读 2,912评论 0 10
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,746评论 0 10
  • 面向对象编程之类 定义一个简单的类 // 定义类,包含field以及方法 // 创建类的对象,并调用其方法 get...
    义焃阅读 782评论 0 2
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,756评论 2 9