How to initialize trait's vals in subtrait?

2019-06-23 11:25发布

问题:

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:

  1. Superclasses are fully initialized before subclasses.
  2. Members are initialized in the order they are declared.
  3. When a val is overridden, it can still only be initialized once.
  4. Like an abstract val, an overridden val will have a default initial value during the construction of superclasses.


标签: scala traits