lazy val 有什么作用?

问题描述 投票:0回答:7

我注意到 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显示

y
是一个
lazy val
,但是它与普通的
val
有什么不同?

scala lazy-evaluation
7个回答
365
投票

它们之间的区别是,

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

这里,当值

x
y
从未被使用时,只有
x
不必要地浪费资源。如果我们假设
y
没有副作用,并且我们不知道它被访问的频率(从不、一次、数千次),那么将其声明为
def
是没有用的,因为我们不想执行它好几次了。

如果您想了解

lazy vals
是如何实现的,请参阅此问题


65
投票

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

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

但是使用惰性值它工作得很好

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()

46
投票

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

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”的值,但不考虑旧的值。

希望这有帮助。


45
投票

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

与 def 一样,lazy val 在被调用之前不会被求值。 但结果会被保存,以便后续调用返回保存的值。 记忆的结果会占用数据结构中的空间,就像 val 一样。

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

惰性值实际上或多或少是作为记忆定义来实现的。 您可以在这里阅读其实施的详细信息:

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


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 

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


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 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 都在对象构造期间初始化
  • 使用惰性关键字将初始化推迟到第一次使用
  • 注意:惰性值不是最终的,因此可能会出现性能缺陷
© www.soinside.com 2019 - 2024. All rights reserved.