懒惰的val怎么办?

时间:2011-09-20 12:11:40

标签: scala lazy-evaluation

我注意到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有什么不同?

7 个答案:

答案 0 :(得分:313)

它们之间的区别在于,val在定义时执行,而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定义)相比,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

此处,从未使用值xy时,只会x不必要地浪费资源。如果我们假设y没有副作用,并且我们不知道它被访问的频率(从不,一次,数千次),将它声明为def是没用的,因为我们不会#39} ; t想多次执行它。

如果您想了解lazy vals的实施方式,请参阅此question

答案 1 :(得分:58)

此功能不仅有助于延迟昂贵的计算,还可用于构建相互依赖或循环结构。例如。这会导致堆栈溢出:

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

但是使用lazy 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()

答案 2 :(得分:38)

我理解答案是给出的,但我写了一个简单的例子,让像我这样的初学者容易理解:

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'的值被考虑在内但不是旧的。

希望这有帮助。

答案 3 :(得分:31)

懒惰的val最容易被理解为&#34; memoized(no-arg)def&#34;。

与def一样,在调用lazy val之前不会对其进行求值。但结果已保存,以便后续调用返回保存的值。 memoized结果占用数据结构中的空间,如val。

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

Lazy vals实际上或多或少地实现了memoized defs。您可以在此处阅读有关其实施细节的信息:

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

答案 4 :(得分: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 

这被称为“早期初始化程序”。有关详细信息,请参阅this SO question

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

scala> lazyEight
I am lazy !
res1: Int = 8
  • 在对象构建期间初始化所有val
  • 使用lazy关键字将初始化推迟到第一次使用
  • 注意:Lazy vals不是最终的,因此可能会显示性能缺陷