trait-多继承详解

33 阅读2分钟

(一) 多个 trait的加载顺序

一个类实现了多个特质之后,所涉及的多个构造器的执行顺序如何确定?

如果有多个父类,则按照从左到右的顺序调用。

代码:

object practice50 {

  trait A {
    println("A 特质构造器")
  }

  trait  B {
    println("B 特质构造器")
  }

  trait  C {
    println("C 特质构造器")
  }

  class Child extends  C with B with A {
    println("child ... ")

  }
  def main(args: Array[String]): Unit = {
    val child = new Child()
  }
}

输出结果为:

  • C 特质构造器
  • B 特质构造器
  • A 特质构造器
  • child ...

多层 trait的加载顺序

先执行父类中的构造器,再执行子类的构造器:如果trait1也有自己的父类,要先执行父类构造器

代码:

trait AA {
  println("AA 特质构造器")
}

trait A extends AA{
  println("A 构造器")
}

trait  B {
  println("B 特质构造器")
}

trait CC {
  println("CC 特质构造器")
}

trait  C extends CC {
  println("C 特质构造器")
}

输出结果:

  • CC 特质构造器
  • C 特质构造器
  • B 特质构造器
  • AA 特质构造器
  • A 构造器
  • child ...

(三)空指针异常

原代码:

trait FileLogger {
  val filename: String

  def writeLog(msg: String): Unit = {
    writer.write(msg)
    writer.close()
  }
}


  class MyWriter extends FileLogger {
    // 继承三个特质a,b,c
    override val filename: String = "test.log"
  }


  def main(args: Array[String]): Unit = {
    val log = new MyWriter
    log.writeLog("测试内容")
  }
}

会出现nullpointerExcaption报错,主要原因是因为fillename是val未初始化

修改方式一:

trait FileLogger {
  val filename: String
  private lazy val writer: FileWriter = new FileWriter(filename)

  def writeLog(msg: String): Unit = {
    writer.write(msg)
    writer.close()
  }
}

用lazy val懒加载的方式初始化filename

修改方式二:

class MyWriter extends {
    // 提前定义:在所有特质(FileLogger+TraitA/B/C)初始化前,赋值 filename
    override val filename: String = "test.log"
  } with FileLogger with TraitA with TraitB with TraitC {
    // 类体无额外代码:filename 已通过提前定义完成初始化
  }

用提前定义的方式直接赋值给filename

trait与类的区别

相同点:类和trait都可以定义成员变量(抽象,具体);继承时都使用extends关键字;

不同点:trait的构造器不能带参数;trait支持多继承;

class A{}
trait B{}

class AB extends A with B{
  // 此处有编辑标记,原代码可能有省略
}