2011-09-20 79 views
193

我注意到Scala提供了lazy vals。但我不明白他們做了什麼。懶惰的val做什麼?

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 

REPL表明ylazy val,但它是如何從一個普通val不同?

回答

267

它們之間的區別在於,定義時執行val,而第一次訪問時執行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 

相反到lazy val被執行一次,然後再也的方法(與def定義)。當操作需要很長時間才能完成,以及何時不確定是否稍後使用時,這可能非常有用。

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 = [email protected] // we have to wait two seconds to the result 

scala> new Y 
res6: Y = [email protected] // this appears immediately 

這裏,當從來沒有使用過的值xy,只有x不必要的資源浪費。如果我們假設y沒有副作用,並且我們不知道它被訪問的頻率(從來沒有,一次,幾千次),將它宣佈爲def是沒有用的,因爲我們不想多次執行它。

如果您想知道lazy vals是如何實現的,請參閱此question

+55

作爲補充:@ViktorKlang在Twitter上發佈:[「鮮爲人知的斯卡拉事實:如果初始化一個懶惰的val拋出一個excel 它會嘗試在下次訪問時重新初始化val。「](https://twitter.com/#!/viktorklang/status/104483846002704384) –

51

此功能不僅有助於延遲昂貴的計算,還有助於構建相互依賴或循環結構。例如。這導致一個堆棧溢出:

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 

但隨着懶瓦爾斯它工作正常

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() 
+0

但是,如果您的toString方法輸出」foo「,將導致相同的StackOverflowException,屬性。無論如何,「懶惰」的好例子! –

19

而且lazy是沒有循環依賴是有用的,如下面的代碼:

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

object Y extends X { val x = "Hello" } 
Y 

訪問Y現在會拋出空指針異常,因爲x尚未初始化。 以下,不過,做工精細:

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

object Y extends X { lazy val x = "Hello" } 
Y 

編輯:下面也將工作:

object Y extends { val x = "Hello" } with X 

這就是所謂的 「早期初始化」。有關更多詳細信息,請參閱this SO question

+11

你能澄清爲什麼在調用父構造函數之前,Y的聲明沒有立即初始化第一個例子中的變量「x」? – Ashoat

+2

因爲超類構造函數是第一個被隱式調用的構造函數。 –

+0

@Ashoat請參閱[此鏈接](https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md)瞭解它未初始化的原因。 – Jus12

21

惰性val最容易理解爲「memoized def」。

就像一個def,一個懶惰的val不會被評估,直到它被調用。但結果被保存,以便後續調用返回保存的值。記憶結果在數據結構中佔用空間,就像val一樣。

正如其他人所提到的,惰性val的用例是推遲昂貴的計算直到需要並存儲它們的結果,並解決值之間的某些循環依賴關係。

懶惰的vals實際上或多或少地作爲memoized defs來實現。你可以在這裏讀到他們的實現細節:

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

23

我明白,給出的答案是,但我寫了一個簡單的例子,使之易於理解,適合初學者和我一樣:

var x = { println("x"); 15 } 
lazy val y = { println("y"); x+1 } 
println("-----") 
x = 17 
println("y is: " + y) 
的上面的代碼

輸出是:

x 
----- 
y 
y is: 18 

如可以看到的,當它的初始化x被打印出來,但是,當它的INI不打印ÿ以相同的方式進行tialized(我已經在這裏故意將x作爲var來解釋y何時被初始化)。接下來,當y被調用時,它被初始化以及最後'x'的值被考慮,但不是舊的。

希望這會有所幫助。

0
scala> lazy val lazyEight = { 
    | println("I am lazy !") 
    | 8 
    | } 
lazyEight: Int = <lazy> 

scala> lazyEight 
I am lazy ! 
res1: Int = 8 
  • 所有瓦爾斯在對象構造期間
  • 使用懶關鍵字來初始化推遲到第一次使用初始化
  • 注意:懶惰的丘壑是不是最終版本,因此可能會顯示性能弊端