Reputation: 132
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
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
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