问题
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?
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/27781837/how-to-initialize-traits-vals-in-subtrait