1

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?

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
SwiftMango
  • 15,092
  • 13
  • 71
  • 136
  • missing `s` in `number`? – Andrey Tyukin Jul 19 '18 at 15:07
  • Possible duplicate of [In Scala, what is an "early initializer"?](https://stackoverflow.com/questions/4712468/in-scala-what-is-an-early-initializer) – Hüseyin Zengin Jul 19 '18 at 15:10
  • @HüseyinZengin that seems somehow too vaguely related, no? Another non-duplicate: [Scala initialization order of vals](https://stackoverflow.com/questions/14568049/scala-initialization-order-of-vals) (there, the OP simply swapped order of `val`s, no abstract classes, no def-by-val override). A somewhat better duplicate: [NPE when overriding parameterless method with a field](https://stackoverflow.com/questions/23313143/null-pointer-exception-when-overriding-a-parameterless-method-with-a-field), but the answer could be much more detailed. – Andrey Tyukin Jul 19 '18 at 15:12
  • @AndreyTyukin Well you are right about the question itself not being a exact duplicate but this question is basically a result of early init. and the answer is perfectly covering this question IMHO – Hüseyin Zengin Jul 19 '18 at 15:17

1 Answers1

3

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

  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).

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

Possible workarounds:

  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

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:

  1. Scala overridden value: parent code is run but value is not assigned at parent.

  2. Assertion with require in abstract superclass creates NPE

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • Thanks for the explanation. Though it strikes me when Scala allows this loophole/pitfall without default warning about that... – SwiftMango Jul 19 '18 at 16:14
  • @texasbruce Well, *object initialization* is just hard. The fact that it works at all in languages with inheritance, traits, exceptions, garbage collection, class-loaders, built-in singletons, and multithreading is a miracle... – Andrey Tyukin Jul 19 '18 at 16:39
  • It should be disallowed to override a method using `val` in Scala to avoid this issue. Only allow `def` and `lazy val` – SwiftMango Jul 19 '18 at 17:33