Writing a simple example from Odersky’s book resulted in the following problem:
// AbstractElement.scala
abstract class AbstractElement {
val contents: Array[String]
val height: Int = contents.length // line 3
}
class UnifiedElement(ch: Char, _width: Int, _height: Int) extends AbstractElement { // line 6
val contents = Array.fill(_height)(ch.toString() * _width)
}
object AbstractElement {
def create(ch: Char): AbstractElement = {
new UnifiedElement(ch, 1, 1) // line 12
}
}
,
// ElementApp.scala
import AbstractElement.create
object ElementApp {
def main(args: Array[String]): Unit = {
val e1 = create(' ') // line 6
println(e1.height)
}
}
The compiler throws the following trace:
Exception in thread "main" java.lang.NullPointerException
at AbstractElement.<init>(AbstractElement.scala:3)
at UnifiedElement.<init>(AbstractElement.scala:6)
at AbstractElement$.create(AbstractElement.scala:12)
at ElementApp$.main(ElementApp.scala:6)
at ElementApp.main(ElementApp.scala)
So the compiler thinks that contents is still null, but I defined it in UnifiedContainer!
Things get even more weird when I replace val with def and evrth works perfect!
Could you please xplain this behaviour?
Here is a great article by Paul P that explains the initialization order intricacies in Scala. As a rule of thumb, you should never use abstract
vals. Always use abstractdefs andlazy vals.