J'ai remarqué que Scala fournit lazy vals
. Mais je ne comprends pas ce qu'ils font.
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
Le REPL montre que y
est un lazy val
, mais en quoi est-il différent d'un val
normal?
La différence entre eux est qu'une val
est exécutée lorsqu'elle est définie, alors qu'un lazy val
est exécuté lors de son premier accès.
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
Contrairement à une méthode (définie avec def
), un lazy val
est exécuté une fois, puis jamais plus. Cela peut être utile lorsqu'une opération prend beaucoup de temps et lorsqu'il n'est pas certain de son utilisation ultérieure.
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
Ici, lorsque les valeurs x
et y
ne sont jamais utilisées, seul x
gaspille inutilement des ressources. Si nous supposons que y
n'a pas d'effets secondaires et que nous ne savons pas à quelle fréquence il est utilisé (jamais, des milliers de fois), il est inutile de le déclarer comme def
, car nous ne le faisons pas vouloir l'exécuter plusieurs fois.
Si vous voulez savoir comment lazy vals
sont implémentés, voyez ceci question .
Cette fonctionnalité permet non seulement de retarder les calculs coûteux, mais est également utile pour construire des structures dépendantes mutuelles ou cycliques. Par exemple. cela conduit à un débordement de pile:
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
Mais avec des vals paresseux cela fonctionne bien
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()
Je comprends que la réponse est donnée mais j’ai écrit un exemple simple pour le rendre facile à comprendre pour les débutants comme moi:
var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)
La sortie du code ci-dessus est:
x
-----
y
y is: 18
Comme on peut le voir, x est imprimé quand il est initialisé, mais y n'est pas imprimé quand il est initialisé de la même manière (j'ai pris x comme var intentionnellement ici - pour expliquer quand y est initialisé). Ensuite, lorsque y est appelé, il est initialisé et la valeur du dernier "x" est prise en compte, mais pas l’ancien.
J'espère que cela t'aides.
Un val paresseux est plus facilement compris comme un " mémoized (no-arg) def".
Comme un def, un val paresseux n'est pas évalué tant qu'il n'est pas appelé. Mais le résultat est enregistré afin que les invocations suivantes renvoient la valeur enregistrée. Le résultat mémo occupe de la place dans votre structure de données, comme un val.
Comme d'autres l'ont mentionné, les cas d'utilisation d'une valeur paresseuse consistent à différer les calculs coûteux jusqu'à leur utilisation, à stocker leurs résultats et à résoudre certaines dépendances circulaires entre les valeurs.
Les valeurs paresseuses sont en fait plus ou moins mises en œuvre comme des mémos. Vous pouvez lire sur les détails de leur mise en œuvre ici:
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
De plus, lazy
est utile sans dépendances cycliques, comme dans le code suivant:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { val x = "Hello" }
Y
L'accès à Y
lève maintenant une exception de pointeur null, car x
n'est pas encore initialisé. Ce qui suit fonctionne cependant bien:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { lazy val x = "Hello" }
Y
EDIT: ce qui suit fonctionnera également:
object Y extends { val x = "Hello" } with X
Ceci est appelé un "initialisateur précoce". Voir this SO question pour plus de détails.
Une démonstration de lazy
- telle que définie ci-dessus - exécution lorsque défini vs exécution lors de l'accès: (à l'aide de 2.12.7 scala Shell)
// 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