7

我在 JAXMag 的 Scala 特刊中遇到了以下代码:

package com.weiglewilczek.gameoflife

case class Cell(x: Int, y: Int) {
  override def toString = position
  private lazy val position = "(%s, %s)".format(x, y)
}

上面代码中的使用是否lazy val比下面的代码提供了更多的性能?

package com.weiglewilczek.gameoflife

case class Cell(x: Int, y: Int) {
  override def toString = "(%s, %s)".format(x, y)
}

还是只是不必要的优化?

4

5 回答 5

19

关于惰性 val 需要注意的一点是,虽然它们只计算一次,但对它们的每次访问都受到双重检查锁定包装器的保护。这是必要的,以防止两个不同的线程同时尝试初始化该值并产生有趣的结果。现在双重检查锁定非常有效(现在它实际上在 JVM 中工作),并且在大多数情况下不需要获取锁,但是比简单的值访问开销更大。

此外(并且有些明显),通过缓存对象的字符串表示,您明确地权衡 CPU 周期以换取内存使用量的可能大幅增加。“def”版本中的字符串可以被垃圾收集,而“lazy val”版本中的字符串不会。

最后,与性能问题一样,如果没有基于事实的基准测试,基于理论的假设几乎没有任何意义。如果不进行分析,您将永远无法确定,因此不妨尝试一下。

于 2010-10-07T15:59:15.120 回答
13

toString可以直接用 a 覆盖lazy val

scala> case class Cell(x: Int, y: Int) {
     |   override lazy val toString = {println("here"); "(%s, %s)".format(x, y)}
     | }
defined class Cell

scala> {val c = Cell(1, 2); (c.toString, c.toString)}
here
res0: (String, String) = ((1, 2),(1, 2))

注意 adef不能覆盖 a val——你只能使成员在子类中更稳定。

于 2010-10-07T16:16:20.753 回答
1

在第一个片段position中,将只计算一次,按需toString调用 [when|if] 方法。在第二个片段中,toString每次调用方法时都会重新评估 body。鉴于这一点x并且y无法更改,它是没有意义的,toString应该存储价值。

于 2010-10-07T15:39:22.823 回答
0

根据定义,案例类是不可变的。toString 返回的任何值本身也是不可变的。因此,通过使用惰性 val 来“缓存”这个值是有意义的。另一方面,提供的 toString 实现仅比所有案例类提供的默认 toString 做得更多。如果一个普通案例类 toString 在下面使用了一个惰性 val,我不会感到惊讶。

于 2010-10-07T15:38:00.203 回答
0

对我来说看起来像是一个微优化。JVM 有足够的能力处理这种情况。

于 2010-10-07T15:58:19.953 回答