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
?
In scala, you can override only those variables which are declared by using val keyword in both classes.
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.
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.
- Superclasses are fully initialized before subclasses.
- 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.
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:
- Superclasses are fully initialized before subclasses.
- 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With