web-dev-qa-db-ja.com

lazy valは何をしますか?

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 は、ylazy valであることを示していますが、通常のvalとはどう違いますか?

229
kiritsuku

それらの違いは、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

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

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

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

314
kiritsuku

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

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

しかし、怠zyな値ではうまく動作します

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

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

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」の値が考慮されますが、古い値は考慮されません。

お役に立てれば。

39
Mital Pritmani

遅延valは、「 memoized (no-arg)def」として最も簡単に理解できます。

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

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

遅延値は、実際には、メモされたdefとして多少なりとも実装されています。実装の詳細については、こちらをご覧ください。

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

32
tksfz

また、lazyは、次のコードのように、循環依存関係なしで便利です。

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

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

Yはまだ初期化されていないため、xにアクセスするとnullポインター例外がスローされます。ただし、以下は正常に機能します。

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 を参照してください。

19
Jus12

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
2
pjames
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

scala> lazyEight
I am lazy !
res1: Int = 8
  • すべての値はオブジェクトの構築中に初期化されます
  • Lazyキーワードを使用して、最初の使用まで初期化を延期します
  • 注意:遅延値は最終的なものではないため、パフォーマンス上の欠点を示す可能性があります
1
user2989087