为什么使用 val 实现抽象方法并在 val 表达式中从超类调用 return NullPointerException

Why does implement abstract method using val and call from superclass in val expression return NullPointerException

我有一个抽象 class 和一个未实现的方法 numbers , returns 一个数字列表,这个方法用于另一个 val 属性 初始化:

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

实施 class 使用 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)

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

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

  1. 分配一块内存,space足够val calcNumbersval numbers,初始设置为0
  2. 运行 基础 class Foo 的初始化程序,它在初始化 calcNumbers.
  3. 时尝试调用 numbers.map
  4. 运行 子 class MainFoo 的初始化器,其中它将 numbers 初始化为 List(1, 2, 3).

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

可能的解决方法:

  1. 使numbersMainFoo成为def
  2. 使numbersMainFoo成为lazy val
  3. 使calcNumbersFoo成为def
  4. 使calcNumbersFoo成为lazy val

每个解决方法都可以防止急切的值初始化对未初始化的值 numbers.

调用 numbers.map

FAQ 提供了一些其他解决方案,它还提到了(昂贵的)编译器标志 -Xcheckinit


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

  1. .