Odpowiedzi:
Różnica między nimi polega na tym, że a val
jest wykonywane, gdy jest zdefiniowane, a a lazy val
jest wykonywane, gdy jest uzyskiwane po raz pierwszy.
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
W przeciwieństwie do metody (zdefiniowanej za pomocą def
) a lazy val
jest wykonywane raz, a potem nigdy więcej. Może to być przydatne, gdy operacja zajmuje dużo czasu i nie ma pewności, czy zostanie później użyta.
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
Tutaj, gdy wartości x
i y
nigdy nie są używane, tylko x
niepotrzebnie marnuje zasoby. Jeśli przypuszczamy, że y
nie ma żadnych skutków ubocznych i nie wiemy, jak często jest on uzyskiwany (nigdy, raz, tysiące razy), nie ma sensu go deklarować, def
ponieważ nie chcemy go wykonywać kilka razy.
Jeśli chcesz wiedzieć, jak lazy vals
są realizowane, zobacz to pytanie .
Lazy<T>
w .NET
Ta funkcja pomaga nie tylko opóźniać kosztowne obliczenia, ale jest także przydatna do budowy wzajemnie zależnych lub cyklicznych struktur. Np. Prowadzi to do przepełnienia stosu:
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
Ale w przypadku leniwych vals działa dobrze
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()
Rozumiem, że odpowiedź jest podana, ale napisałem prosty przykład, aby ułatwić zrozumienie dla początkujących takich jak ja:
var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)
Wyjście powyższego kodu to:
x
-----
y
y is: 18
Jak widać, x jest drukowane podczas inicjalizacji, ale y nie jest drukowane, gdy jest inicjowany w ten sam sposób (wziąłem x jako var celowo tutaj - aby wyjaśnić, kiedy y jest inicjalizowany). Następnie, gdy wywoływane jest y, jest ono inicjalizowane, a także uwzględniana jest wartość ostatniego „x”, ale nie stara.
Mam nadzieję że to pomoże.
Leniwy val jest najłatwiejszy do zrozumienia jako „ zapamiętany (bez arg) def”.
Podobnie jak def, leniwa val nie jest oceniana, dopóki nie zostanie wywołana. Ale wynik jest zapisywany, więc kolejne wywołania zwracają zapisaną wartość. Zapamiętany wynik zajmuje miejsce w strukturze danych, podobnie jak val.
Jak wspomnieli inni, przypadki użycia leniwej wartości polegają na odkładaniu drogich obliczeń, aż będą potrzebne i na przechowywaniu ich wyników oraz na rozwiązaniu pewnych cyklicznych zależności między wartościami.
Leniwe vale są w rzeczywistości implementowane mniej więcej jako zapamiętane def. O szczegółach ich wdrożenia możesz przeczytać tutaj:
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
lazy
Jest także przydatny bez cyklicznych zależności, jak w poniższym kodzie:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { val x = "Hello" }
Y
Uzyskiwanie dostępu Y
spowoduje teraz zgłoszenie wyjątku zerowego wskaźnika, ponieważ x
nie został jeszcze zainicjowany. Działa to jednak dobrze:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { lazy val x = "Hello" }
Y
EDYCJA: działają również następujące elementy:
object Y extends { val x = "Hello" } with X
Nazywa się to „wczesnym inicjatorem”. Zobacz to SO pytanie, aby uzyskać więcej informacji.
Demonstracja lazy
- jak zdefiniowano powyżej - wykonania po zdefiniowaniu vs wykonanie przy dostępie: (przy użyciu powłoki Scala 2.12.7)
// 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