lazy val
is a language feature where the initialization of a val
is delayed until it is accessed for the first time. After that point, it acts just like a regular val
.
To use it add the lazy
keyword before val
. For example, using the REPL:
scala> lazy val foo = {
| println("Initializing")
| "my foo value"
| }
foo: String = <lazy>
scala> val bar = {
| println("Initializing bar")
| "my bar value"
| }
Initializing bar
bar: String = my bar value
scala> foo
Initializing
res3: String = my foo value
scala> bar
res4: String = my bar value
scala> foo
res5: String = my foo value
This example demonstrates the execution order. When the lazy val
is declared, all that is saved to the foo
value is a lazy function call that hasn't been evaluated yet. When the regular val
is set, we see the println
call execute and the value is assigned to bar
. When we evalute foo
the first time we see println
execute - but not when it's evaluated the second time. Similarly, when bar
is evaluated we don't see println
execute - only when it is declared.
Initialization is computationally expensive and usage of val
is rare.
lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum}
if (scala.util.Random.nextInt > 1000) {
println(tiresomeValue)
}
tiresomeValue
takes a long time to calculate, and it's not always used. Making it a lazy val
saves unnecessary computation.
Resolving cyclic dependencies
Let's look at an example with two objects that need to be declared at the same time during instantiation:
object comicBook {
def main(args:Array[String]): Unit = {
gotham.hero.talk()
gotham.villain.talk()
}
}
class Superhero(val name: String) {
lazy val toLockUp = gotham.villain
def talk(): Unit = {
println(s"I won't let you win ${toLockUp.name}!")
}
}
class Supervillain(val name: String) {
lazy val toKill = gotham.hero
def talk(): Unit = {
println(s"Let me loosen up Gotham a little bit ${toKill.name}!")
}
}
object gotham {
val hero: Superhero = new Superhero("Batman")
val villain: Supervillain = new Supervillain("Joker")
}
Without the keyword lazy
, the respective objects can not be members of an object. Execution of such a program would result in a java.lang.NullPointerException
. By using lazy
, the reference can be assigned before it is initialized, without fear of having an uninitialized value.