You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala中lazy val继承规则解析:构造期val为null问题解惑

Scala中lazy val解决父类初始化时子类val为null的原理解析

先看普通val的初始化场景:

abstract class A {
    val x: String
    println(s"A: x is $x")
}

class B extends A {
    val x: String = "foo"
    println(s"B: x is $x")    
}

new B()

运行输出:

A: x is null
B: x is foo

普通val的初始化逻辑

Scala遵循父类优先初始化的规则:

  • 创建B实例时,先执行父类A的构造流程。此时子类B中声明的x还未进入赋值阶段(子类初始化在父类之后),A中访问的x实际是子类B里x的默认初始值(引用类型默认值为null),所以打印出null。
  • 父类A初始化完成后,才会执行子类B的构造逻辑,给x赋值为"foo",此时打印B中的x就是正确值。

再看添加lazy后的场景:

abstract class A {
    val x: String
    println(s"A: x is $x")
}

class B extends A {
    lazy val x: String = "foo"
    println(s"B: x is $x")    
}

new B()

运行输出:

A: x is foo
B: x is foo

lazy val的核心机制

lazy val的本质是延迟求值+线程安全的缓存,它改变了初始化的触发时机:

  • 子类B用lazy val声明x时,不会在子类初始化阶段立即赋值,而是生成一个代理对象占位。
  • 父类A初始化时访问x,会触发这个代理对象的求值逻辑——Scala允许在父类构造期间调用子类lazy val的求值(这是语言层面的特殊支持),此时直接计算出"foo"并缓存这个值。
  • 后续子类B中打印x时,直接复用已经缓存好的值,不需要再次计算。

澄清误解

不是lazy val比普通val更早初始化,而是:

  • 普通val的赋值严格在子类初始化阶段(父类初始化完成后)执行;
  • lazy val的赋值是在第一次被访问时执行,而父类初始化时的访问刚好触发了这次赋值,所以父类能拿到正确值,而普通val要等父类走完才会赋值,父类访问时只能拿到默认的null。

内容的提问来源于stack exchange,提问作者A_P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 04:45:32