Var,Val 和 Def

由于 val 在语义上是静态的,因此无论它们出现在代码中,它们都会就地初始化。当在抽象类和特征中使用时,这会产生令人惊讶和不良行为。

例如,假设我们想制作一个名为 PlusOne 的特征,它定义了一个包装的 Int 的增量操作。由于 Ints 是不可变的,所以在初始化时已知值加 1,之后永远不会改变,因此在语义上它是一个 val。但是,以这种方式定义它将产生意想不到的结果。

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

无论你使用什么价值 i,在返回的对象上调用 .incr 将始终返回 1.这是因为 val incr 在特征中初始化,在扩展类之前,并且此时 i 仅具有默认值 0 。 (在其他情况下,它可能会填充 Nilnull 或类似的默认值。)

因此,一般规则是避免在依赖于抽象字段的任何值上使用 val。相反,使用 lazy val,它在需要时不进行评估,或使用 def,每次调用时都会进行评估。但请注意,如果在初始化完成之前 lazy val 强制要求 lazy val 进行评估,则会发生相同的错误。

可以在这里找到一个小提琴(用 Scala-Js 编写,但同样的行为适用)