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

Scala中val不可变却可被子类重写的原理疑问

问题解答

你疑惑的核心点在于:Scala中val是不可变的,但子类却能重写基类定义的val,既然基类构造时该val已创建,子类为何还能重写?

关键本质:Scala的val是「私有字段+公共getter方法」的组合

Scala里的val并非单纯的不可变变量,它会被编译为两部分:

  • 一个私有不可变字段(存储实际值,外部无法直接访问)
  • 一个公共getter方法(对外提供取值的入口)

子类的override val实际做了两件事:

  1. 在子类内部创建一个全新的私有不可变字段,存储子类自定义的取值
  2. 重写基类的getter方法,让它返回子类自己的字段值

结合你的代码实例拆解执行流程

当创建Dog实例时,执行顺序是:

  1. 先执行基类Animal的构造函数,初始化基类的私有talk字段为"Ha Ha!"
  2. 再执行子类Dog的构造函数,初始化子类自己的私有talk字段为"Woof Woof!",同时替换掉基类的getter方法

当你通过happyAnimal.talk访问时,实际调用的是当前实例的getter方法:

  • 若实例是Animal,调用基类的getter,返回"Ha Ha!"
  • 若实例是Dog,调用子类重写后的getter,返回"Woof Woof!"
  • 若实例是Cat,因为没有重写talk,所以依然调用基类的getter,返回"Ha Ha!"

简单来说:你看到的不是「修改了基类的val」,而是子类提供了自己的val实现,通过重写getter方法让外部访问到子类的取值。

补充:构造顺序的潜在坑

如果基类构造时直接调用了val的getter方法(比如在基类构造里打印talk),会出现「提前初始化」问题——因为此时子类的override val还未完成初始化,重写的getter会返回字段的默认值(比如null)。例如:

class Animal() {
  println(talk) // 基类构造时调用getter
  val talk: String = "Ha Ha!"
}

class Dog extends Animal {
  override val talk = "Woof Woof!"
}

new Dog() // 输出null,子类的talk还未初始化

这是因为Scala严格遵循「基类构造先于子类」的顺序,此时子类的字段还没赋值,重写的getter只能返回默认值。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:51:57