怠惰なvalは何をしますか?


248

Scalaが提供してlazy valsいることに気づきました。しかし、私は彼らが何をするのかわかりません。

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のショーyですlazy valが、どのようにそれは通常と異なっていますかval

回答:


335

これらの違いは、a valは定義さlazy valれたときに実行されるのに対し、a は最初にアクセスされたときに実行されるということです。

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

メソッド(で定義def)とlazy valは対照的に、a は一度だけ実行され、その後は二度と実行されません。これは、操作の完了に時間がかかる場合や、後で使用するかどうかが不明な場合に役立ちます。

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 = X@262505b7 // we have to wait two seconds to the result

scala> new Y
res6: Y = Y@1555bd22 // this appears immediately

ここで、値xとはyのみ、使用されることはありませんx不必要なリソースを無駄にします。我々はそれが仮定した場合yは副作用を持っていないし、我々はそれがアクセスされた頻度を知らないこと(決して、一回、何千回も)としてそれを宣言しても無駄ではないdef、我々はそれを数回実行する必要はありませんから。

lazy vals実装方法を知りたい場合は、この質問を参照してください。


65
補足として、@ ViktorKlangがTwitterに投稿しました
Peter Schmitz、2011

@PeterSchmitzそして私はこれがひどいのを見つけました。Lazy<T>.NET と比較する
Pavel Voronin

61

この機能は、高価な計算を遅らせるだけでなく、相互依存構造または循環構造を構築するのにも役立ちます。例えば、これはスタックオーバーフローにつながります:

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

しかし、遅延valsを使用すると、正常に動作します

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

しかし、toStringメソッドが「foo」属性を出力する場合、同じStackOverflowExceptionが発生します。とにかく「怠惰」の良い例!!!
Fuad Efendi 2016

39

私は答えが出ていることを理解していますが、私のような初心者が理解しやすいように簡単な例を書きました:

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は初期化時に出力されますが、同じ方法で初期化するとyは出力されません(ここでは、xをvarとして意図的に取り上げています-yが初期化されるときを説明しています)。次にyが呼び出されると、初期化され、最後の「x」の値が考慮されますが、古い値は考慮されません。

お役に立てれば。


35

遅延valは、「メモされた(引数なしの)def」として最も簡単に理解できます。

defと同様に、lazy valは呼び出されるまで評価されません。ただし、結果は保存されるため、以降の呼び出しでは保存された値が返されます。メモ化された結果は、valのようにデータ構造のスペースを占有します。

他の人が述べたように、遅延valの使用例は、高価な計算が必要になるまで延期して結果を格納し、値間の特定の循環依存関係を解決することです。

レイジーヴァルは実際には、多かれ少なかれメモ化されたdefとして実装されています。あなたはここでそれらの実装の詳細について読むことができます:

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


1
むしろ「引数をとらないメモ化されたdef」のようです。
Andrey Tyukin

19

また、lazy次のコードのように、環状の依存関係なしに有用です。

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

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

はまだ初期化されていないYため、アクセスするとnullポインタ例外がスローされます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 

これは「初期イニシャライザ」と呼ばれます。詳細については、このSOの質問を参照してください。


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

2
スーパークラスコンストラクターは、暗黙的に呼び出される最初のコンストラクターであるためです。
StevoSlavić2014年

@Ashoat 初期化されない理由については、このリンクを参照しください。
Jus12

4

lazy上記で定義したとおり、定義時の実行とアクセス時の実行のデモ:(2.12.7 scalaシェルを使用)

// compiler says this is ok when it is lazy
scala> lazy val t: Int = t 
t: Int = <lazy>
//however when executed, t recursively calls itself, and causes a StackOverflowError
scala> t             
java.lang.StackOverflowError
...

// when the t is initialized to itself un-lazily, the compiler warns you of the recursive call
scala> val t: Int = t
<console>:12: warning: value t does nothing other than call itself recursively
   val t: Int = t

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

scala> lazyEight
I am lazy !
res1: Int = 8
  • オブジェクトの構築中にすべての値が初期化されます
  • lazyキーワードを使用して、最初の使用まで初期化を延期します
  • 注意:遅延valは最終的なものではないため、パフォーマンス上の欠点を示す可能性があります
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.