为什么使用val实现抽象方法并从val表达式中的超类调用返回NullPointerException [英] Why does implement abstract method using val and call from superclass in val expression return 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
:
- 分配一块内存,为
val calcNumbers
和val numbers
分配足够的空间,初始设置为0
. - 运行基类
Foo
的初始化程序,它尝试在初始化calcNumbers
时调用numbers.map
. - 运行子类
MainFoo
的初始化程序,将numbers
初始化为List(1, 2, 3)
.
- Allocate a block of memory, with enough space for
val calcNumbers
andval numbers
, initially set to0
. - Run the initializer of the base class
Foo
, where it attempts to invokenumbers.map
while initializingcalcNumbers
. - Run the initializer of the child class
MainFoo
, where it initializesnumbers
toList(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
.
可能的解决方法:
- 使
MainFoo
中的numbers
成为def
- 使
MainFoo
中的numbers
成为lazy val
- 使
Foo
中的calcNumbers
成为def
- 使
Foo
中的calcNumbers
成为lazy val
- Make
numbers
inMainFoo
adef
- Make
numbers
inMainFoo
alazy val
- Make
calcNumbers
inFoo
adef
- Make
calcNumbers
inFoo
alazy 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:
Assertion with require
in abstract superclass creates NPE
这篇关于为什么使用val实现抽象方法并从val表达式中的超类调用返回NullPointerException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!