1

我有一个带有未实现方法的抽象类,该方法numbers返回一个数字列表,并且此方法用于另一个 val 属性初始化:

abstract class Foo {
  val calcNumbers = numbers.map(calc)
  def numbers: List[Double]
}

实现类使用 val 表达式实现:

class MainFoo extends Foo {
  val numbers = List(1,2,3)
}

这编译得很好,但在运行时它会抛出 NullPointerException 并指向以下行val calcNumbers

[error] (run-main-0) java.lang.ExceptionInInitializerError
[error] java.lang.ExceptionInInitializerError
...
[error] Caused by: java.lang.NullPointerException
...

但是,当我将实现的方法更改为 def 时,它可以工作:

def numbers = List(1,2,3)

这是为什么?它与初始化顺序有关吗?由于没有编译时错误/警告,将来如何避免这种情况?Scala 如何允许这种不安全的操作?

4

1 回答 1

3

这是您的代码在初始化时尝试执行的操作MainFoo

  1. 分配一块内存,有足够的空间存放val calcNumbersval numbers,最初设置为0
  2. 运行基类的初始化程序,它会在初始化Foo时尝试调用。numbers.mapcalcNumbers
  3. 运行子类的初始化程序,它在MainFoo哪里初始化。numbersList(1, 2, 3)

由于numbers当您尝试在 中访问它时尚未初始化val calcNumbers = ...,您会得到一个NullPointerException.

可能的解决方法:

  1. 做一个numbers_MainFoodef
  2. 做一个numbers_MainFoolazy val
  3. 做一个calcNumbers_Foodef
  4. 做一个calcNumbers_Foolazy val

每个解决方法都可以防止急切的值初始化numbers.map在未初始化的值上调用numbers

常见问题解答提供了一些其他解决方案,并且还提到了(昂贵的)编译器标志-Xcheckinit


您可能还会发现这些相关答案很有用:

  1. Scala 覆盖值:父代码已运行,但未在 parent 分配值

  2. require抽象超类中的断言创建 NPE

于 2018-07-19T15:38:05.537 回答