I tried using an abstract val
in a trait to initialize another value. I got a NullPointerException
. I boiled the behaviour down to a minimal test case:
trait MessagePrinter {
val message: String
println(message)
}
class HelloPrinter extends MessagePrinter {
val message = "Hello World"
}
val obj = new HelloPrinter()
println(obj.message)
This little program yields the following result:
null
Hello World
I was under the impression that a val may never change. Is this expected behaviour or is it a compiler bug? How can I work around this issue and print Hello World
during initialization?
By section 5.1 of the Scala specification, super classes are initialized first. Even though vals
cannot normally be reinstantiated, they do start with a default initial value during construction. You can either use def
, which has different semantics:
trait MessagePrinter {
def message: String
println(message)
}
class HelloPrinter extends MessagePrinter {
def message = "Hello World"
}
Or you might consider switching things around like so:
class HelloPrinter extends { val message = "Hello World" } with MessagePrinter
In which case the super classes are evaluated in order, so that the MessagePrinter
initialization should work as desired.
You should use def
in both cases.
One of the sources describing this behaviour is "Scala Puzzlers" Puzzler 4:
The following rules control the initialization and overriding behavior
of vals:
- Superclasses are fully initialized before subclasses.
- Members are initialized in the order they are declared.
- When a val is overridden, it can still only be initialized once.
- Like an abstract val, an overridden val will have a default initial value during the construction of superclasses.