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
とはどう違いますか?
それらの違いは、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 を参照してください。
この機能は、高価な計算を遅らせるだけでなく、相互に依存する構造または周期的な構造を構築するのにも役立ちます。例えば。これにより、スタックオーバーフローが発生します。
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()
私は答えが与えられていることを理解していますが、私のような初心者のために簡単に理解できるように簡単な例を書きました:
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」の値が考慮されますが、古い値は考慮されません。
お役に立てれば。
遅延valは、「 memoized (no-arg)def」として最も簡単に理解できます。
Defと同様に、遅延valは呼び出されるまで評価されません。ただし、結果は保存されるため、後続の呼び出しでは保存された値が返されます。メモされた結果は、valのようにデータ構造内のスペースを占有します。
他の人が言及したように、遅延valの使用例は、高価な計算が必要になるまで延期して結果を保存し、値間の特定の循環依存関係を解決することです。
遅延値は、実際には、メモされたdefとして多少なりとも実装されています。実装の詳細については、こちらをご覧ください。
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
また、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 を参照してください。
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
scala> lazy val lazyEight = {
| println("I am lazy !")
| 8
| }
lazyEight: Int = <lazy>
scala> lazyEight
I am lazy !
res1: Int = 8