在异常的情况下,`lazy val`就像`def`一样是正确的行为吗?

dk1*_*k14 10 scala runtimeexception

我注意到lazy val重复计算几次(如果发生异常):

scala> lazy val aaa = {println("calc"); sys.error("aaaa")}
aaa: Nothing = <lazy>

scala> aaa
calc
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at .aaa$lzycompute(<console>:7)
  at .aaa(<console>:7)
  ... 33 elided

scala> aaa
calc
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at .aaa$lzycompute(<console>:7)
  at .aaa(<console>:7)
  ... 33 elided
Run Code Online (Sandbox Code Playgroud)

不应该是这样的:

scala> aaa
calc
java.lang.RuntimeException: Not Initialized! 
caused by
java.lang.RuntimeException: aaaa

scala> aaa
java.lang.RuntimeException: Not Initialized! 
caused by
java.lang.RuntimeException: aaaa  
Run Code Online (Sandbox Code Playgroud)

kos*_*sii 6

这篇文章中,他们很好地解释lazy val了Scala编译器是如何编译的.基本上,如果表达式的评估失败,则lazy val不会设置包含其数据的指示符位信令.

UPDATE1:

我认为第一种方法的一个原因可能是第二种方法可以通过使用两个lazy vals 来模拟,而不会给底层实现带来多个volatile变量的负担:

scala> lazy val _aaa = Try {println("calc"); sys.error("aaaa")}
_aaa: scala.util.Try[Nothing] = <lazy>

scala> lazy val aaa = _aaa.get
aaa: Nothing = <lazy>

scala> aaa
calc
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at $anonfun$_aaa$1.apply(<console>:10)
  at $anonfun$_aaa$1.apply(<console>:10)
  at scala.util.Try$.apply(Try.scala:191)
  at ._aaa$lzycompute(<console>:10)
  at ._aaa(<console>:10)
  at .aaa$lzycompute(<console>:11)
  at .aaa(<console>:11)
  ... 33 elided

scala> aaa
java.lang.RuntimeException: aaaa
  at scala.sys.package$.error(package.scala:27)
  at $anonfun$_aaa$1.apply(<console>:10)
  at $anonfun$_aaa$1.apply(<console>:10)
  at scala.util.Try$.apply(Try.scala:191)
  at ._aaa$lzycompute(<console>:10)
  at ._aaa(<console>:10)
  at .aaa$lzycompute(<console>:11)
  at .aaa(<console>:11)
  ... 33 elided
Run Code Online (Sandbox Code Playgroud)

UPDATE2:

正如@Silly Freak在评论中提出的那样,

scala> lazy val _aaa = Try {println("calc"); sys.error("aaaa")}
_aaa: scala.util.Try[Nothing] = <lazy>

scala> def aaa = _aaa.get
aaa: Nothing
Run Code Online (Sandbox Code Playgroud)

可能会更好,因为我们可以避免两个lazy vals.

  • @kosii `_aaa` 是“成功”或“失败”。如果成功,那么`get` 没有副作用,可以多次调用它。如果失败,则`get` 会抛出异常,因此`lazy` 无论如何都不会缓存它。在我看来,让 `aaa` 成为一个 `lazy val` 并没有在语义上添加任何东西,不是吗?并且 `lazy val` 比 `def` 更昂贵。 (2认同)