问题
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]
}
The implementing class implements using a val expression:
class MainFoo extends Foo {
val numbers = List(1,2,3)
}
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
...
However when I changed the implemented method to def, it works:
def numbers = List(1,2,3)
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?
回答1:
Here is what your code attempts to do when it initializes MainFoo
:
- 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)
.
Since numbers
is not initialized yet when you try to access it in val calcNumbers = ...
, you get a NullPointerException
.
Possible workarounds:
- Make
numbers
inMainFoo
adef
- Make
numbers
inMainFoo
alazy val
- Make
calcNumbers
inFoo
adef
- Make
calcNumbers
inFoo
alazy val
Every workaround prevents that an eager value initialization invokes numbers.map
on a non-initialized value numbers
.
The FAQ offers a few other solutions, and it also mentions the (costly) compiler flag -Xcheckinit
.
You might also find these related answers useful:
Scala overridden value: parent code is run but value is not assigned at parent.
Assertion with require in abstract superclass creates NPE
来源:https://stackoverflow.com/questions/51426017/why-does-implement-abstract-method-using-val-and-call-from-superclass-in-val-exp