egor7
egor7

Reputation: 4941

Scala initialization order

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

Answers (2)

Rafaël
Rafaël

Reputation: 1027

Thanks for the interesting question ! My guess (after spending some time on Scastie) would be this order of initialization :

  1. Arguments : in your case, str is the first value to be defined
  2. Parent : in your case, Element
  3. Child : in your case, 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

Mario Galic
Mario Galic

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

Related Questions