简体   繁体   English

惰性 val 有什么作用?

[英]What does a lazy val do?

I noticed that Scala provide lazy vals .我注意到 Scala 提供了lazy vals But I don't get what they do.但我不明白他们在做什么。

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

The REPL shows that y is a lazy val , but how is it different from a normal val ? REPL显示y是一个lazy val ,但它与普通val有什么不同?

The difference between them is, that a val is executed when it is defined whereas a lazy val is executed when it is accessed the first time.它们之间的区别在于, 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

In contrast to a method (defined with def ) a lazy val is executed once and then never again.与方法(用def定义)相反, lazy val执行一次,然后再也不执行。 This can be useful when an operation takes long time to complete and when it is not sure if it is later used.当操作需要很长时间才能完成并且不确定以后是否会使用时,这会很有用。

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

Here, when the values x and y are never used, only x unnecessarily wasting resources.在这里,当值xy从未被使用时,只有x不必要地浪费资源。 If we suppose that y has no side effects and that we do not know how often it is accessed (never, once, thousands of times) it is useless to declare it as def since we don't want to execute it several times.如果我们假设y没有副作用并且我们不知道它被访问的频率(从不,一次,数千次),那么将它声明为def是没有用的,因为我们不想多次执行它。

If you want to know how lazy vals are implemented, see this question .如果您想知道lazy vals是如何实现的,请参阅此问题

This feature helps not only delaying expensive calculations, but is also useful to construct mutual dependent or cyclic structures.此功能不仅有助于延迟昂贵的计算,而且对于构建相互依赖或循环结构也很有用。 Eg this leads to an stack overflow:例如,这会导致堆栈溢出:

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

But with lazy vals it works fine但是使用惰性 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()

I understand that the answer is given but I wrote a simple example to make it easy to understand for beginners like me:我知道答案已经给出,但我写了一个简单的例子,让像我这样的初学者容易理解:

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

Output of above code is:上面代码的输出是:

x
-----
y
y is: 18

As it can be seen, x is printed when it's initialized, but y is not printed when it's initialized in same way (I have taken x as var intentionally here - to explain when y gets initialized).可以看出,x 在初始化时打印,但 y 在以相同方式初始化时不打印(我在这里特意将 x 作为 var - 来解释 y 何时被初始化)。 Next when y is called, it's initialized as well as value of last 'x' is taken into consideration but not the old one.接下来,当 y 被调用时,它会被初始化并考虑最后一个 'x' 的值,但不考虑旧的值。

Hope this helps.希望这可以帮助。

A lazy val is most easily understood as a " memoized (no-arg) def".惰性 val 最容易理解为“记忆化(无参数)定义”。

Like a def, a lazy val is not evaluated until it is invoked.与 def 一样,惰性 val 在被调用之前不会被评估。 But the result is saved so that subsequent invocations return the saved value.但结果会被保存,以便后续调用返回保存的值。 The memoized result takes up space in your data structure, like a val.记忆的结果会占用数据结构中的空间,就像一个 val。

As others have mentioned, the use cases for a lazy val are to defer expensive computations until they are needed and store their results, and to solve certain circular dependencies between values.正如其他人所提到的,惰性 val 的用例是推迟昂贵的计算,直到需要它们并存储它们的结果,并解决值之间的某些循环依赖关系。

Lazy vals are in fact implemented more or less as memoized defs. Lazy vals 实际上或多或少是作为 memoized defs 实现的。 You can read about the details of their implementation here:您可以在此处阅读有关其实施的详细信息:

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

Also lazy is useful without cyclic dependencies, as in the following code: lazy在没有循环依赖的情况下也很有用,如以下代码所示:

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

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

Accessing Y will now throw null pointer exception, because x is not yet initialized.访问Y现在将抛出空指针异常,因为x尚未初始化。 The following, however, works fine:但是,以下工作正常:

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

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

EDIT: the following will also work:编辑:以下也将起作用:

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

This is called an "early initializer".这称为“早期初始化程序”。 See this SO question for more details.有关更多详细信息,请参阅此 SO 问题

A demonstration of lazy - as defined above - execution when defined vs execution when accessed: (using 2.12.7 scala shell) 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
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • All vals are initialized during object construction所有 vals 在对象构造期间初始化
  • Use lazy keyword to defer initialization until first usage使用 lazy 关键字将初始化推迟到第一次使用
  • Attention : Lazy vals are not final and therefore might show performance drawbacks注意: Lazy vals 不是最终的,因此可能会显示性能缺陷

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM