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
相关产品推荐
相关产品推荐

