我在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)
}
或者只是一个不必要的优化案例?
答案 0 :(得分:19)
关于延迟val的一件事是,虽然它们只计算一次,但每次访问它们都受到双重检查锁定包装的保护。这对于防止两个不同的线程同时尝试使用热闹的结果来初始化值是必要的。现在,双重检查锁定是非常有效的(现在它实际上在JVM中工作),并且在大多数情况下不需要锁定获取,但是比简单的值访问有更多的开销。
此外(有些明显),通过缓存对象的字符串表示形式,您可以明确地权衡CPU周期,因为内存使用量可能会大幅增加。 “def”版本中的字符串可以被垃圾收集,而“lazy val”版本中的字符串则不会。
最后,正如性能问题一样,基于理论的假设在没有基于事实的基准测试的情况下几乎没有任何意义。如果没有剖析,你永远不会知道,所以不妨试试看看。
答案 1 :(得分:13)
toString
可以使用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))
请注意,def
可能无法覆盖val
- 您只能在子类中使成员更稳定。
答案 2 :(得分:1)
在第一个代码段position
中,只需按需计算一次[when | if] toString
方法。在第二个代码段中,每次调用方法时都会重新评估toString
正文。鉴于x
和y
无法更改,这是无意义的,应该存储toString
值。
答案 3 :(得分:0)
根据定义,案例类是不可变的。 toString返回的任何值本身也是不可变的。因此,通过利用惰性val来实质上“缓存”该值是有意义的。另一方面,提供的toString实现只比所有case类提供的默认toString更多。如果香草案例类toString在下面使用了一个懒惰的val,我不会感到惊讶。
答案 4 :(得分:0)
对我来说看起来像微优化。 JVM足以处理此类情况。