The following code, when entered in REPL
abstract class A { val aSet: Set[Int]; require(aSet.contains(3)) }
class B extends A { val aSet = Set(4,5,6) }
new B()
gives a null point exception, rather than an invariant failure.
What would be the best idiom to solve this problem?
Similar questions:
Code Contracts: Invariants in abstract class
Private constructor in abstract class Scala?
and also online commentary: https://gist.github.com/jkpl/4932e8730c1810261381851b13dfd29d
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:
- Why is my abstract or overridden val null?
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