代码改错(Lazy)

53 阅读3分钟

在 Scala 中,lazy 是一个关键字,用于延迟初始化变量,其核心作用是:变量的初始化不会在声明时执行,而是在第一次被使用时才执行

具体作用与特性:

  1. 延迟初始化被 lazy 修饰的变量(lazy val),在定义时不会立即分配内存或执行初始化逻辑,只有当它第一次被访问(读取或使用)时,才会执行初始化代码。
  2. 保证初始化一次即使被多次访问,lazy val 也只会初始化一次,后续访问直接使用已初始化的值(类似单例效果)。
  3. 解决初始化顺序问题在类或特质继承关系中,父类 / 特质的初始化可能早于子类,导致依赖的变量尚未赋值(如你之前遇到的 filename 为 null 的问题)。lazy 可以通过延迟初始化,确保依赖的变量在使用时已被正确初始化。
  4. 优化资源加载对于创建成本高的对象(如文件流、网络连接、大型数据结构),lazy 可以避免不必要的初始化(如果变量始终未被使用,则不会消耗资源)。

注意事项:

  • lazy 只能修饰 val(不可变变量),不能修饰 var(可变变量),因为 var 可能被多次赋值,与 “初始化一次” 的特性冲突。
  • 过度使用 lazy 可能隐藏初始化顺序问题,或因延迟执行导致调试困难,需合理使用。

简单说,lazy 的核心价值是:按需初始化,且只初始化一次,尤其适合解决依赖顺序问题和优化资源加载。

Lazy:

懒加载效果

||

这个对象不会立刻去创建,而是等到你需要使用的时候才去创建

错误代码

package level02
import java.io.FileWriter
/*
*/
object class13 {

  trait FileLogger{
    val filename:String
    val writer = new FileWriter(filename)
    def writeLog(msg:String): Unit = {
      writer.write(msg)
      writer.close()
    }
  }

  class MyWriter extends FileLogger {
    override val filename: String = "test.log"
  }

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

报错界面

image.png

报错原因:

这个错误的原因是 Scala 特质(trait)的初始化顺序问题。具体来说,FileLogger 特质中的 writer 初始化时,filename 还未被赋值(仍为 null),导致 FileWriter 构造器抛出 NullPointerException

问题分析:

  1. 初始化顺序规则:当类继承特质时,特质的初始化先于子类。在你的代码中:

    • MyWriter 类继承 FileLogger 特质。
    • 初始化 MyWriter 时,会先初始化 FileLogger 特质。
    • 特质初始化时会执行 val writer = new FileWriter(filename),但此时子类的 filename"test.log")还未被赋值(特质中 filename 只是声明,未初始化),导致 filename 为 null,进而 FileWriter 构造失败。

代码改正

package level02
import java.io.FileWriter
/*
 */
object class13 {

  trait FileLogger{
    
    println("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 = "test1.log"
  } // 继承 三个特质A,B,C

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