Reputation: 4941
In the Programming in Scala (Ch. 10 on "Composition & Inheritance") book there is an example which causes some misunderstandings. This is the extracted part:
abstract class Element {
def contents: Array[String]
val someProperty: String = {
println("=== Element")
contents(0)
}
}
class UniformElement(
str: String
) extends Element {
val s = str
println("=== UniformElement.s " + s)
def contents = Array(s) // error
//def contents = Array(str) // ok
}
val e = new UniformElement("str")
println(e.someProperty)
For some reason the initialization of superclass occures before s
initialisation:
scala example.scala
=== Element
=== UniformElement.s str
null
Why does the alternative work without s
(see commented line in code)?
Upvotes: 3
Views: 120
Reputation: 1027
Thanks for the interesting question ! My guess (after spending some time on Scastie) would be this order of initialization :
str
is the first value to be definedElement
UniformElement
So, if I try to put it in a single class order, it goes like this :
class UniformElement{
// Argument init
val str = "str"
// Super constructor
def contents: Array[String]
val someProperty: String = {
println("=== Element")
contents(0)
}
// Child constructor
val s = str
println("=== UniformElement.s " + s)
def contents = Array(s) // error
//def contents = Array(str) // ok
}
The trick is that, to init someProperty
, scala need to evaluate contents(0)
and find contents
definition. But when finding definition, s
is not yet defined (and str
is).
So final 'runtime' process:
class UniformElement{
// Argument init
val str = "str"
// Super constructor with contents replaced by definition
val someProperty: String = {
println("=== Element")
Array(s)(0) // error : s doesn't exists !
// Array(str)(0) // ok : str exists
}
// Child constructor
val s = str
println("=== UniformElement.s " + s)
def contents = Array(s) // error
//def contents = Array(str) // ok
}
To convince yourself, you can try :
println(e.someProperty) // null => s wasn't defined
println(e.contents(0)) // str => s is now defined
Feel free to ask for clarification if needed.
Upvotes: 3
Reputation: 48410
The issue is field values are null
until constructor completes, and super constructor is referencing indirectly value s
which is initialised by child constructor, but the child constructor has not yet completed. The situation looks something like this
class UniformElement {
def <init>(str: String) = {
super.<init>()
s = str
}
}
where we can see if we replace super.<init>()
with
val someProperty: String = {
println("=== Element")
contents(0)
}
that it executes before
s = str
Initialisation order issues can often be addressed by changing eager val s
into lazy like so
class UniformElement(str: String) extends Element {
lazy val s = str
println("=== UniformElement.s " + s)
def contents = Array(s)
}
which now outputs
=== Element
=== UniformElement.s str
str
Upvotes: 4