Scala的trait-多继承详解

42 阅读3分钟

内容:

1.多个trait的加载顺序

2.多层trait的加载顺序

3.空指针异常

4.trait与类的区别

多个trait的加载顺序

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

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

代码如下:

package level02
/*
*   特质
*   trait:实现多继承
*   构造器的执行顺序
*   1,先父 后子
*   2,如果是多继承,有多个trait,按书写顺序从左到右
 */
object class016 {
  trait A {
    println("A 特质构造器")
  }
  trait B {
    println("B 特质构造器")
  }
  trait C {
    println("C 特质构造器")
  }
  class Child() extends C with B with A {
    println("child...")
  }// 继承 三个特质A,B,C
  
  def main(args: Array[String]): Unit = {
    val child = new Child()
  }
}

多层trait的加载顺序

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

代码如下:

package level02
/*
*   特质
*   trait:实现多继承
*   构造器的执行顺序
*   1,先父 后子
*   2,如果是多继承,有多个trait,按书写顺序从左到右
 */
object class016 {
  trait AA {
    println("A 特质构造器")
  }
  trait A extends AA {
    println("A 构造器")
  }
  trait B {
    println("B 特质构造器")
  }
  trait CC {
    println("CC 特质构造器")
  }
  trait C extends CC {
    println("C 构造器")
  }

  class Child() extends C with B with A {
    println("child...")
  }// 继承 三个特质A,B,C

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

输出结果:

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

空指针异常

代码如下: 这段代码有错误。

package level02

import java.io.FileWriter

object class017 {
  trait FileLogger {
    val filename: String
    val writer = new FileWriter(filename)
    def writeLog(msg:String):Unit = {
      writer.write(msg)
      writer.close()
    }
  }
  class MyWriter extends {
    override val filename:String = "test.log"
  }

  def main(args: Array[String]): Unit = {
    val log = new MyWriter()
    log.writeLog("测试内容")
  }
}
\scala004\out\production\scala004;C:\Users\admin\.ivy2\cache\org.scala-lang\scala-library\jars\scala-library-
2.13.18.jar;C:\Users\admin\.ivy2\cache\org.scala-lang\scala-reflect\jars\scala-reflect-2.13.18.jar level02.class017 Exception in thread "main" 
java.lang.NullPointerException at 
java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:226) at 
java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:123) at 
java.base/java.io.FileWriter.<init>(FileWriter.java:66) at 
level02.class017$FileLogger.$init$(class017.scala:8)
at level02.class017$MyWriter.<init>(class017.scala:14) at level02.class017$.main(class017.scala:19) at 
level02.class017.main(class017.scala)

报错如何解决,哪里有错误?

问题分析

初始化顺序问题

-   `FileLogger` 中的 `writer` 在构造时立即初始化
-   `writer` 初始化时使用了 `filename` 字段
-   但在子类 `MyWriter` 中,`filename` 被重写为 `"test.log"`
-   由于父类先于子类初始化,`writer` 初始化时 `filename` 还是 `null`

根据问题分析,解决问题

方案1:使用提前定义

代码如下:

package level02

import java.io.FileWriter

object class017 {
  trait FileLogger {
    val filename: String
    val writer = new FileWriter(filename)
    def writeLog(msg:String):Unit = {
      writer.write(msg)
      writer.close()
    }
  }
  class MyWriter extends {
    override val filename:String = "test.log"
  }with FileLogger{
    override val writer = new FileWriter(filename)
  }

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

成功运行

方案2:使用懒加载

代码如下:

package level02

import java.io.FileWriter

object class017 {
  trait FileLogger {
    val filename: String
    //懒加载效果
    //这个对象不会立刻创建,在你使用时才会创建。
    lazy val writer = new FileWriter(filename)
    
    def writeLog(msg:String):Unit = {
      writer.write(msg)
      writer.close()
    }
  }
  class MyWriter extends FileLogger{
    println("MyWriter")
    override val filename:String = "test.log"
  }

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

成功运行

trait与类的区别

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

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