lazy val是一种语言功能,其中a的初始化val会延迟到首次访问它之前。此后,它的作用就像常规的val。
要使用它,请lazy在之前添加关键字val。例如,使用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本示例演示了执行顺序。当lazy val被宣布,被保存到所有的foo值是一个懒惰的函数调用尚未评估。val设置常规后,我们会看到该println调用已执行,并且该值已分配给bar。当我们foo第一次println评估时,我们看到执行-但是在第二次评估时却没有。同样,当bar被评估时,我们看不到println执行-仅在声明时执行。
初始化在计算上是昂贵的,并且val很少使用。
lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum}
if (scala.util.Random.nextInt > 1000) {
  println(tiresomeValue)
}
tiresomeValue需要很长时间才能计算出来,而且并不总是使用它。使其lazy val节省不必要的计算。
解决循环依赖
让我们看一个示例,其中在实例化期间需要同时声明两个对象:
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")
}
如果没有关键字lazy,则各个对象不能成为对象的成员。执行这样的程序会导致程序错误java.lang.NullPointerException。通过使用lazy,可以在初始化引用之前对其进行分配,而不必担心具有未初始化的值。