Scala Trait中val循环赋值合法性及重写行为疑问
解答Scala Trait中Val初始化与重写的疑问
嘿,这个问题刚好戳中Scala初始化机制里容易绕晕的点,我来给你一步步拆解清楚~
1. 为什么Trait A的循环赋值能合法编译?Scala是怎么解析的?
核心在于Scala对val的两步初始化机制,以及trait的初始化规则:
Scala里所有的val字段,在真正执行你写的赋值代码之前,都会先被设置为对应类型的默认初始值:比如Int是0,引用类型(像String)是null。
回到Trait A的代码:
trait A { val a: Int = b val b: Int = a }
当这个trait被初始化时,流程是这样的:
- 第一步:先给
a和b都分配默认值0; - 第二步:执行你写的赋值语句:
- 先执行
a = b:这时候b还是默认值0,所以a被赋值为0(和默认值一致,看起来没变化); - 再执行
b = a:这时候a已经是0了,所以b还是0;
- 先执行
最后a和b自然都是0。那为什么编译器允许这种循环引用的写法?因为Scala知道val会先做默认初始化,不会出现“变量未定义”的情况——哪怕赋值逻辑是循环的,也能安全完成初始化,只是结果是默认值而已。
2. 子类重写Trait的Val时,Trait里用这个Val会取什么值?
这得分两种情况,核心差异在于子类Val的初始化时机:
情况1:在类体中重写Val(比如类C)
看代码:
class C extends A { override val a: Int = 10 }
当你实例化C时,初始化顺序是:
- 先初始化Trait A:按照刚才说的两步走,
a和b都被设为0; - 再初始化子类C的字段:执行
override val a = 10,把a的值覆盖成10;
所以最后a是子类重写的10,但b是Trait初始化时用默认值0得到的结果——因为Trait初始化的时候,子类的a还没被赋值,Trait里用的是自己的a字段(默认值0)。
情况2:在构造参数中重写Val(比如类D)
看代码:
class D(override val a: Int) extends A val d = new D(10)
这里的override val a是主构造函数的参数化字段,它的初始化时机非常早——在Trait A初始化之前就完成了。
实例化D(10)的流程:
- 先初始化构造参数里的
a:直接把a设为10; - 再初始化Trait A:
- 这时候Trait里所有对
a的引用,都是指向子类已经初始化好的a(值为10); - 执行
b = a时,a已经是10了,所以b被赋值为10; - 至于Trait里的
a = b:因为a已经被子类重写为不可变的val,而且已经完成初始化,这个赋值操作会被忽略,所以a保持10不变;
- 这时候Trait里所有对
最后d.a和d.b自然都是10。
内容的提问来源于stack exchange,提问作者Shamshad Alam
相关产品推荐
相关产品推荐

