Reputation: 129
I have this scenario
trait D {
def someMethod(): Unit = {}
}
trait C {
val locations: Seq[Int]
someSomethingWithLocations() // calling this in constructor
def someSomethingWithLocations(): Unit = {
print(locations.length)
}
}
class B extends D with C {
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
}
def main(args: Array[String]): Unit = {
val b = new B
}
When I run this code someSomethingWithLocations
throws null pointer exception because constructor of class B is not called yet, hence locations is not initialized.
And If I change declaration of class B to
class B extends{
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
} with D with C
compiler complains that someMethod() is not found. How can i resolve this issue?
For now I have moved declaration of locations to a different trait and my program works as expected but I would like to avoid unnecessary trait if possible.
Upvotes: 0
Views: 313
Reputation: 10764
The solution you tried is so called early initializer. You can't call someMethod
inside it, because:
val
definitionsD
which contains someMethod
is mixed in after the early initializer, so it can't be used yetBut anyway, early initializers should be considered a last resort when fixing initialization order. Before falling back to it, you should first try some less hacky solutions:
Instead of defining or overriding a val
from trait, try making it a constructor parameter. Constructor parameters which are val
s are initialized before any constructor code is invoked. Here you can do it by introducing an intermediate abstract class:
abstract class AbstractB(val locations: Seq[Int])
class B extends AbstractB(1 :: 2 :: 3 :: Nil) with D with C {
someMethod()
}
Make your val
a lazy val
:
class B extends D with C {
override lazy val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
someMethod()
}
This way locations
will be initialized not in the constructor of class B
but simply on first access, which in your case will be the constructor of trait C
(note that in this case the lazy
keyword makes the field be initialized earlier than normally rather than later, which is a common intuition about lazy val
s).
lazy val
seems like a simple and easy solution, but I would recommend first trying constructor parameters if possible. This is because the lazy val
itself may access another val
which may not yet be initialized at that point. This way the problem escalates to other val
s and in the end you may find yourself having to declare all of them as lazy
.
If you still want to use an early initializer, you need to move the method call outside of it and put it into constructor:
class B extends {
override val locations: Seq[Int] = 1 :: 2 :: 3 :: Nil
} with D with C {
someMethod()
}
Upvotes: 3