2010-10-07 45 views
7

使用延遲val的我遇到了下面的代碼在JAXMag的斯卡拉特刊:用於緩存字符串表示

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

懶惰應推動值的計算,直到它被調用。但是,很好的問題。它是否創建了一個對象來保存用於評估的函數? – wheaties 2010-10-07 15:32:46

+2

只要你知道,你可以通過調用'(x,y).toString'來獲得相同的字符串格式。 – 2010-10-07 17:00:43

回答

0

根據定義,案例類是不可變的。 toString返回的任何值本身也是不可變的。因此,通過利用惰性val來實質上「緩存」該值是有意義的。另一方面,提供的toString實現只比所有案例類提供的默認toString多一點。如果一個香草案例類toString在下面使用了一個懶惰的val,我不會感到驚訝。

+3

不可變的定義? scala>案例類Foo(var x:Int) 已定義的類Foo – 2010-10-08 09:04:52

+0

不知道'var'修飾符可以用於案例類數據成員。我應該說,「在問題中給出的案例類是不可變的,儘管我仍然認爲你在那裏做了純粹的邪惡。 – 2010-10-09 17:47:01

1

在第一個片段position將被計算一次,根據需要,[當|如果] toString方法被調用。在第二個片段中,toString正文將在每次調用該方法時重新評估。由於xy無法更改,因此無意義,應存儲toString值。

0

看起來像一個微型優化給我。 JVM足以處理這種情況。

+6

JVM中絕對沒有任何東西會記錄這樣的局部範圍以外的方法結果(即,如果您在同一個方法中調用.toString兩次,JVM有可能緩存簡單的方法返回。)持久地緩存這樣的結果遠遠超出了它的範圍,這是很好的,因爲(在這種情況下),自動記憶會導致顯着的內存佔用增加 – 2010-10-07 16:02:49

19

懶惰vals有一點需要注意的是,雖然它們只計算一次,但每次對它們的訪問都受到雙重檢查的鎖定封裝的保護。這對於防止兩個不同的線程同時嘗試初始化該值以及令人愉快的結果是必要的。現在,雙重檢查鎖定非常高效(現在它實際上可以在JVM中工作),並且在大多數情況下不需要獲取鎖定,但是比簡單的值訪問有更多的開銷。

通過緩存對象的字符串表示形式,可以明顯地取消CPU週期,以減少可能大量增加的內存使用量。 「def」版本中的字符串可以被垃圾回收,而「lazy val」版本中的字符串則不會。最後,性能問題總是如此,如果沒有基於事實的基準測試,基於理論的假設幾乎意味着什麼。你永遠不會知道沒有分析,所以不妨試試看看。

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 - 您只能成員的子類更穩定。

+4

+1,不知道'def'可以被'lazy val覆蓋'。 :) – missingfaktor 2010-10-07 16:39:25