Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do overridden variables get the wrong values in Scala?

Tags:

scala

I have a class A in Scala, like this one:

class A {
  val a = 3
  lazy val b = 2
  println("a = " + a)
  println("b = " + b)
}

Next, I extend this class to another class B:

class B extends A {
  override val a = 4
  override lazy val b = 3
}

Now, when I create an object of class B, I get the following output:

a = 0  //the default value of int is zero `0` in Scala
b = 3

Whereas I was expecting the output to be:

a = 3
b = 2

My question is how do the println() functions in class A come to know about the values defined in class B, but only of b and not of a?

like image 230
himanshuIIITian Avatar asked Apr 01 '18 05:04

himanshuIIITian


People also ask

How do you override variables in Scala?

In scala, you can override only those variables which are declared by using val keyword in both classes.

What is difference between Val and VAR in Scala?

The difference between val and var is that val makes a variable immutable — like final in Java — and var makes a variable mutable. Because val fields can't vary, some people refer to them as values rather than variables.


2 Answers

docs.scala-lang.org - tutorials - initialization-order provides a complete explanation.


In order to see clearer, let's print the same thing in class B as in class A:

class A {
  val a = 3
  lazy val b = 2
  println("A: a = " + a)
  println("A: b = " + b)
}

class B extends A {
  override val a = 4
  override lazy val b = 3
  println("B: a = " + a)
  println("B: b = " + b)
}

In this case, new B() produces:

A: a = 0
A: b = 3
B: a = 4
B: b = 3

The initialization order of non-lazy val variables is given by:

In the absence of “early definitions” (see below), initialization of strict vals is done in the following order.

  1. Superclasses are fully initialized before subclasses.
  2. Otherwise, in declaration order.

Thus A is fully initialized before B.

But val can't be initialized more than once. So internally Scala gives it a default value (0 for an Int, null for a String, ..) when dealing first with the superclass A.

Thus it prints 0 at first (when initializing A) and then 4 (when initializing B).


Using lazy vals is the solution proposed by the scaladoc to bypass this limitation.

like image 114
Xavier Guihot Avatar answered Oct 23 '22 23:10

Xavier Guihot


Why lazy val works as expected? Because val is marked by lazy will be initialized on its first access.

Why non-lazy does not work as expected? Because of initialization order.

non-lazy vals are initialized in the following order:

  1. Superclasses are fully initialized before subclasses.
  2. Otherwise, in declaration order.

In your example you're trying to access an override val, but an override val will appear to be null during the construction of superclasses.

Code with more logs to see an initialization order:

class A {
  println("First line of A")
  val strictVal: String = {
    println("A.strictVal called")
    "strictVal of A"
  }
  lazy val lazyVal: String = {
    println("A.lazyVal called")
    "lazyVal of A"
  }

  println(s"lazyVal = $lazyVal")
  println(s"strictVal = $strictVal")

  println("Last line of A")
}

class B extends A {
  println("First line of B")
  override val strictVal: String = {
    println("B.strictVal called")
    "strictVal of B"
  }

  override lazy val lazyVal: String = {
    println("B.lazyVal called")
    "lazyVal of B"
  }
  println("Last line of B")
}

Output for new B():

First line of A
A.strictVal called
B.lazyVal called
lazyVal = lazyVal of B
strictVal = null
Last line of A
First line of B
B.strictVal called
Last line of B
like image 34
Artavazd Balayan Avatar answered Oct 24 '22 01:10

Artavazd Balayan