When you declare a val, several things happen:
- The compiler makes sure that enough space is allocated for the variable when an instance of the class is initialized
- An accessor-method is created
- Initializers that setup the initial values of the variables are created.
Your code
abstract class A { val aSet: Set[Int]; require(aSet.contains(3)) }
class B extends A { val aSet = Set(4,5,6) }
new B()
is roughly equivalent to
abstract class A {
private var aSet_A: Set[Int] = null
def aSet: Set[Int] = aSet_A
require(aSet.contains(3))
}
class B extends A {
private var aSet_B: Set[Int] = Set(4,5,6)
override def aSet: Set[Int] = aSet_B
}
new B()
so, the following happens:
- Memory for
aSet_A and aSet_B is allocated and set to null.
- Initializer of
A is run.
require on aSet.contains(3) is invoked
- Since
aSet is overridden in B, it returns aSet_B.
- Since
aSet_B is null, an NPE is thrown.
To avoid this, you can implement aSet as a lazy variable:
abstract class A {
def aSet: Set[Int]
require(aSet.contains(3))
}
class B extends A {
lazy val aSet = Set(4,5,6)
}
new B()
This throws the requirement failed exception:
java.lang.IllegalArgumentException: requirement failed
Obligatory link to Scala's FAQ:
List of related questions:
- Why does implement abstract method using val and call from superclass in val expression return NullPointerException
- Overridden value parent code is run but value is not assigned at parent