根据特征中的抽象def获取val的null

jjs*_*jst 10 scala

在我的特性中混合val和def时,我看到了一些初始化的怪异.可以通过以下示例总结这种情况.

我有一个特性提供了一个抽象字段,我们称之为它fruit,应该在子类中实现.它还在val中使用该字段:

scala> class FruitTreeDescriptor(fruit: String) {
     |   def describe = s"This tree has loads of ${fruit}s"
     | }
defined class FruitTreeDescriptor

scala> trait FruitTree {
     |   def fruit: String
     |   val descriptor = new FruitTreeDescriptor(fruit)
     | }
defined trait FruitTree
Run Code Online (Sandbox Code Playgroud)

当覆盖fruita时def,事情按预期工作:

scala> object AppleTree extends FruitTree {
     |   def fruit = "apple"
     | }
defined object AppleTree

scala> AppleTree.descriptor.describe
res1: String = This tree has loads of apples
Run Code Online (Sandbox Code Playgroud)

但是,如果我覆盖fruit使用val...

scala> object BananaTree extends FruitTree {
     |   val fruit = "banana"
     | }
defined object BananaTree

scala> BananaTree.descriptor.describe
res2: String = This tree has loads of nulls
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

mik*_*łak 5

简单来说,在你打电话的时候:

val descriptor = new FruitTreeDescriptor(fruit)
Run Code Online (Sandbox Code Playgroud)

的构造函数BananaTree尚未获得运行的机会。这意味着 的值fruit仍然是null,即使它是val

这是的非声明性初始化的众所周知的怪癖的vals一个子案例,可以用一个更简单的例子来说明:

class A {                           
     val x = a
     val a = "String"
}

scala> new A().x
res1: String = null
Run Code Online (Sandbox Code Playgroud)

(尽管幸运的是,在这种特殊情况下,编译器会检测到正在发生的事情并会发出警告。)

为了避免这个问题,声明fruit为 a lazy val,这将强制评估。