懒惰的val怎么办?

kir*_*uku 237 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
Run Code Online (Sandbox Code Playgroud)

该REPL表明y是一个lazy val,但它是如何从一个正常的不同val?

kir*_*uku 323

它们之间的区别在于,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
Run Code Online (Sandbox Code Playgroud)

与方法(定义为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
Run Code Online (Sandbox Code Playgroud)

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

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

  • 作为补充:@ViktorKlang在Twitter上发布:["鲜为人知的Scala事实:如果lazy val的初始化引发异常,它将尝试在下次访问时重新初始化val."](https://twitter.com/ #!/ viktorklang /状态/ 104483846002704384) (63认同)

Lan*_*dei 60

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

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
Run Code Online (Sandbox Code Playgroud)

但是懒惰的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()
Run Code Online (Sandbox Code Playgroud)


Mit*_*ani 39

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

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

上述代码的输出是:

x
-----
y
y is: 18
Run Code Online (Sandbox Code Playgroud)

可以看出,x在初始化时被打印,但是当它以相同的方式初始化时不打印y(我在这里故意将x作为var - 来解释y何时被初始化).接下来,当y被调用时,它被初始化以及最后的'x'的值被考虑但不考虑旧的值.

希望这可以帮助.


tks*_*sfz 34

懒惰的val最容易理解为" memoized(no-arg)def".

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

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

实际上,懒惰的val实际上或多或少地被实现为memoized defs.您可以在此处阅读有关其实施细节的信息:

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

  • 也许更确切地说,是一个“带 0 个参数的记忆定义”。 (2认同)

Jus*_*s12 19

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

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

object Y extends X { val x = "Hello" }
Y
Run Code Online (Sandbox Code Playgroud)

Y现在访问将抛出空指针异常,因为x尚未初始化.但是,以下工作正常:

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

object Y extends X { lazy val x = "Hello" }
Y
Run Code Online (Sandbox Code Playgroud)

编辑:以下也将工作:

object Y extends { val x = "Hello" } with X 
Run Code Online (Sandbox Code Playgroud)

这被称为"早期初始化器".有关详细信息,请参阅此SO问题.

  • 您是否可以澄清为什么在调用父构造函数之前,Y的声明不会立即初始化第一个示例中的变量"x"? (11认同)
  • 因为超类构造函数是第一个被隐式调用的构造函数. (2认同)