How does the initialization of classes in Scala work?

Viewed 436

The code below throws a java.lang.NullPointerException because the trait is initialized prematurely.

trait DummyTrait {
  def intSeq: Seq[Int]
  require(intSeq.exists(_ > 2))
}
object Dummy extends DummyTrait {
  val extraIntSeq: Seq[Int] = Seq(-2,-3)
  override def intSeq = Seq(1,0,4) ++ extraIntSeq
}

Dummy.intSeq

However, the simple change below fixes the issue, but I couldn't understand why.

trait DummyTrait {
  def intSeq: Seq[Int]
  require(intSeq.exists(_ > 2))
}
object Dummy extends DummyTrait {
  lazy val extraIntSeq: Seq[Int] = Seq(-2,-3) // using `def` also works
  override def intSeq = Seq(1,0,4) ++ extraIntSeq
}

Dummy.intSeq

I found this documentation about overriden val being NULL, but it doesn't seem to be applicable to the example above since the fix doesn't involve a variable that is defined in the interface of the superclass.

Also, is the solution presented above an antipattern? As someone who is developing the trait, how should I enforce requirements to abstract values without surprising the user that implements the child class with a potential NullPointerException?

Obs.: I am using Scala version 2.13.0

2 Answers

In the first case the extraIntSeq is initialised in the constructor after super constructor is called hence the NPE

  object Dummy extends DummyTrait {
    private[this] val extraIntSeq: Seq = _;
    ...
    def <init>($outer: O): Dummy.type = {
      Dummy.super.<init>();
      Dummy.this.extraIntSeq = ... // note that it comes AFTER super
      ()
    }
  }

whilst in the latter lazy val is turned into method.

Related