懒惰的val做什么?


247

我注意到Scala提供了lazy vals。但是我不明白他们的所作所为。

scala> val x = 15
x: Int = 15

scala> lazy val y = 13
y: Int = <lazy>

scala> x
res0: Int = 15

scala> y
res1: Int = 13

REPL表明,ylazy val的,但它是如何从一个正常的不同val

Answers:


335

它们之间的区别在于,a val是在定义时执行的,而a lazy val是在首次访问时执行的。

scala> val x = { println("x"); 15 }
x
x: Int = 15

scala> lazy val y = { println("y"); 13 }
y: Int = <lazy>

scala> x
res2: Int = 15

scala> y
y
res3: Int = 13

scala> y
res4: Int = 13

与方法(用定义def)相比,a lazy val将执行一次,然后不再执行。当操作需要很长时间才能完成并且不确定是否以后使用时,此功能很有用。

scala> class X { val x = { Thread.sleep(2000); 15 } }
defined class X

scala> class Y { lazy val y = { Thread.sleep(2000); 13 } }
defined class Y

scala> new X
res5: X = X@262505b7 // we have to wait two seconds to the result

scala> new Y
res6: Y = Y@1555bd22 // this appears immediately

在这里,当从未使用过值x和时y,只会x不必要地浪费资源。如果我们假设它y没有副作用,而且我们不知道它被访问的频率(永远,一次,数千次),则将其声明为无用的,def因为我们不想多次执行它。

如果您想知道如何lazy vals实现,请参阅此问题



@PeterSchmitz我觉得这很糟糕。Lazy<T>在.NET中进行比较
Pavel Voronin

61

此功能不仅有助于延迟昂贵的计算,而且对于构造相互依赖或循环的结构也很有用。例如,这导致堆栈溢出:

trait Foo { val foo: Foo }
case class Fee extends Foo { val foo = Faa() }
case class Faa extends Foo { val foo = Fee() }

println(Fee().foo)
//StackOverflowException

但是使用懒惰的vals可以正常工作

trait Foo { val foo: Foo }
case class Fee extends Foo { lazy val foo = Faa() }
case class Faa extends Foo { lazy val foo = Fee() }

println(Fee().foo)
//Faa()

但是,如果您的toString方法输出“ foo”属性,则会导致相同的StackOverflowException。无论如何,“懒惰”的好例子!!!
Fuad Efendi

39

我知道给出了答案,但是我写了一个简单的示例来使像我这样的初学者容易理解:

var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)

上面代码的输出是:

x
-----
y
y is: 18

可以看出,x在初始化时打印,但是y在以相同方式初始化时不打印(我在这里故意将x当作var-解释y何时初始化)。接下来,当调用y时,将对其进行初始化,并考虑最后一个“ x”的值,但不考虑旧值。

希望这可以帮助。


35

惰性val最容易理解为“已记忆(无参数)def”。

像def一样,惰性val在调用之前不会进行评估。但是将保存结果,以便后续调用返回保存的值。记录的结果会占用数据结构中的空间,例如val。

正如其他人所提到的,惰性val的用例是将昂贵的计算推迟到需要它们并存储其结果之前,并解决值之间的某些循环依赖关系。

实际上,惰性val或多或少地被实现为备忘录的def。您可以在此处阅读有关其实现的详细信息:

http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html


1
也许更像是“带有0个参数的内存化def”。
安德烈(Andrey Tyukin)

19

lazy在没有循环依赖性的情况下也很有用,如以下代码所示:

abstract class X {
  val x: String
  println ("x is "+x.length)
}

object Y extends X { val x = "Hello" }
Y

Y现在访问将引发空指针异常,因为x尚未初始化。但是,以下方法可以正常工作:

abstract class X {
  val x: String
  println ("x is "+x.length)
}

object Y extends X { lazy val x = "Hello" }
Y

编辑:以下也将起作用:

object Y extends { val x = "Hello" } with X 

这称为“早期初始化程序”。有关更多详细信息,请参见此SO问题


11
您能说明为什么在调用父构造函数之前,第一个示例中的Y声明没有立即初始化变量“ x”的原因吗?
Ashoat

2
因为超类构造函数是第一个隐式调用的构造函数。
StevoSlavić2014年

@Ashoat请参阅此链接以获取有关为何未初始化的说明。
2014年

4

lazy-定义如上-执行定义与访问时执行的演示:(使用2.12.7 scala shell)

// compiler says this is ok when it is lazy
scala> lazy val t: Int = t 
t: Int = <lazy>
//however when executed, t recursively calls itself, and causes a StackOverflowError
scala> t             
java.lang.StackOverflowError
...

// when the t is initialized to itself un-lazily, the compiler warns you of the recursive call
scala> val t: Int = t
<console>:12: warning: value t does nothing other than call itself recursively
   val t: Int = t

1
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • 所有val在对象构造过程中初始化
  • 使用lazy关键字将初始化推迟到第一次使用时
  • 注意:惰性val不是最终值,因此可能会显示性能缺陷
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.