2011-09-20 5 views
193

私は、Scalaがlazy valsを提供していることに気付きました。しかし、私は彼らが何をするのか分からない。 REPLレイジーヴァルとは何ですか?

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 

ylazy valであることを示しているが、どのようにそれは通常のvalと異なっているのですか?

答えて

267

の違いは、定義されている場合はvalが実行され、最初にアクセスされた場合はlazy valが実行されます。 lazy valを決して再び、次に実行される(defと定義される)方法とは対照的に

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 

。これは、操作が完了するまでに時間がかかり、後で使用するかどうか不明な場合に役立ちます。ここで

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に副作用がなく、アクセス頻度(1回、1回、何千回)がわからない場合、defと宣言することは無意味です。数回実行したくないからです。

lazy valsの実装方法については、questionをご覧ください。

+55

補足として:@ViktorKlangはTwitterに投稿しました:["ほとんど知られていないScalaの事実:遅延の初期化が の例外を投げた場合""(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が発生します。これは、次のアクセス時にvalを再初期化しようとします。属性。とにかく "怠け者"の素敵な例!!! –

19

lazyに動作しますが、次のコードのように、循環依存関係がなくて便利です。

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

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

へのアクセスxがまだ初期化されていないため、Yはヌルポインタ例外をスローします。 以下、ただし、正常に動作します:

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

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

EDIT:以下も動作します:

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

これは "早期初期化" と呼ばれています。詳細はthis SO questionを参照してください。

+11

親コンストラクタを呼び出す前に、最初の例で変数 "x"をすぐにYの宣言で初期化しない理由を明確にすることはできますか? – Ashoat

+2

スーパークラスのコンストラクタは、最初に暗黙的に呼び出されるコンストラクタです。 –

+0

@Ashoat初期化されていない理由については、[このリンク](https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md)を参照してください。 – Jus12

21

遅延レイアウは、最も簡単に「memoized def」と理解されます。

defの場合と同様に、lazy valは呼び出されるまで評価されません。しかし、結果は保存され、以後の呼び出しで保存された値が返されます。メモの結果は、valのようなデータ構造内の領域を占有します。

他にも言及したように、遅延レイアウトの使用例は、必要になるまで高価な計算を延期し、その結果を格納し、値間の特定の循環依存を解決することです。

実際には、レイジー値はメモに記録されたdefとして多かれ少なかれ実装されています。あなたはここでその実装の詳細を読むことができます:

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だときyは印刷されないよう同じ方法で(私はここで意図的に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 
  • すべてヴァルスは、オブジェクトの構築時に最初の使用まで、初期化を延期する
  • 使用怠惰なキーワードを初期化され
  • 注意:レイジーヴァルスは、パフォーマンスが表示される場合がありますので、最終的とされないが
を欠点
関連する問題