Pedro Igor A. Oliveira
Pedro Igor A. Oliveira

Reputation: 132

How does the initialization of classes in Scala work?

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

Upvotes: 4

Views: 790

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51648

So far you can use early initializer

object Dummy extends {
  val extraIntSeq: Seq[Int] = Seq(-2,-3)
  override val intSeq = Seq(1,0,4) ++ extraIntSeq
} with DummyTrait

In Scala, what is an "early initializer"?

https://dotty.epfl.ch/docs/reference/dropped-features/early-initializers.html

https://contributors.scala-lang.org/t/proposal-to-remove-early-initializers-from-the-language/2144

https://github.com/scala/scala-dev/issues/513

Upvotes: 3

Mario Galic
Mario Galic

Reputation: 48400

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.

Upvotes: 4

Related Questions