为什么使用val实现抽象方法并从val表达式中的超类调用返回NullPointerException [英] Why does implement abstract method using val and call from superclass in val expression return NullPointerException

查看:44
本文介绍了为什么使用val实现抽象方法并从val表达式中的超类调用返回NullPointerException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an abstract class with an unimplemented method numbers that returns a list of numbers, and this method is used in another val property initialization:

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

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

The implementing class implements using a val expression:

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

这编译得很好,但在运行时它抛出一个 NullPointerException 并指向 val calcNumbers 行:

This compiles fine, but at run time it throws a NullPointerException and it points to the line of val calcNumbers:

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

但是,当我将实现的方法更改为 def 时,它起作用了:

However when I changed the implemented method to def, it works:

def numbers = List(1,2,3)

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

Why is that? Does it have something to do with initialization order? How can this be avoided in the future as there is no compile time error/warning? How does Scala allow this unsafe operation?

推荐答案

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

Here is what your code attempts to do when it initializes MainFoo:

  1. 分配一块内存,为val calcNumbersval numbers分配足够的空间,初始设置为0.
  2. 运行基类 Foo 的初始化程序,它尝试在初始化 calcNumbers 时调用 numbers.map.
  3. 运行子类 MainFoo 的初始化程序,将 numbers 初始化为 List(1, 2, 3).
  1. Allocate a block of memory, with enough space for val calcNumbers and val numbers, initially set to 0.
  2. Run the initializer of the base class Foo, where it attempts to invoke numbers.map while initializing calcNumbers.
  3. Run the initializer of the child class MainFoo, where it initializes numbers to List(1, 2, 3).

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

Since numbers is not initialized yet when you try to access it in val calcNumbers = ..., you get a NullPointerException.

可能的解决方法:

  1. 使MainFoo中的numbers成为def
  2. 使MainFoo中的numbers成为lazy val
  3. 使 Foo 中的 calcNumbers 成为 def
  4. 使 Foo 中的 calcNumbers 成为 lazy val
  1. Make numbers in MainFoo a def
  2. Make numbers in MainFoo a lazy val
  3. Make calcNumbers in Foo a def
  4. Make calcNumbers in Foo a lazy val

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

Every workaround prevents that an eager value initialization invokes numbers.map on a non-initialized value numbers.

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

The FAQ offers a few other solutions, and it also mentions the (costly) compiler flag -Xcheckinit.

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

You might also find these related answers useful:

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

使用 require 进行断言 在抽象超类中创建 NPE

Assertion with require in abstract superclass creates NPE

这篇关于为什么使用val实现抽象方法并从val表达式中的超类调用返回NullPointerException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆