Respuestas:
La diferencia entre ellos es que a val
se ejecuta cuando se define, mientras que a lazy val
se ejecuta cuando se accede por primera vez.
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
A diferencia de un método (definido con def
) a lazy val
se ejecuta una vez y luego nunca más. Esto puede ser útil cuando una operación tarda mucho en completarse y cuando no está seguro si se usará más tarde.
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
Aquí, cuando los valores x
y y
nunca se usan, solo se x
desperdician recursos innecesariamente. Si suponemos que y
no tiene efectos secundarios y que no sabemos con qué frecuencia se accede (nunca, una vez, miles de veces), es inútil declararlo def
ya que no queremos ejecutarlo varias veces.
Si desea saber cómo lazy vals
se implementan, consulte esta pregunta .
Lazy<T>
.NET
Esta característica ayuda no solo a retrasar los costosos cálculos, sino que también es útil para construir estructuras mutuamente dependientes o cíclicas. Por ejemplo, esto lleva a un desbordamiento de pila:
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
Pero con vals perezosos funciona 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()
Entiendo que se da la respuesta, pero escribí un ejemplo simple para que sea fácil de entender para principiantes como yo:
var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)
La salida del código anterior es:
x
-----
y
y is: 18
Como se puede ver, x se imprime cuando se inicializa, pero y no se imprime cuando se inicializa de la misma manera (he tomado x como var intencionalmente aquí, para explicar cuándo se inicializa y). Luego, cuando se llama y, se inicializa y se tiene en cuenta el valor de la última 'x', pero no el anterior.
Espero que esto ayude.
Un vago val se entiende más fácilmente como una " definición memorable (sin argumentos )".
Al igual que un def, un val vago no se evalúa hasta que se invoca. Pero el resultado se guarda para que las invocaciones posteriores devuelvan el valor guardado. El resultado memorizado ocupa espacio en su estructura de datos, como un val.
Como otros han mencionado, los casos de uso para un val perezoso son diferir cálculos costosos hasta que sean necesarios y almacenar sus resultados, y resolver ciertas dependencias circulares entre valores.
De hecho, los vals perezosos se implementan más o menos como defs memorables. Puede leer sobre los detalles de su implementación aquí:
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
También lazy
es útil sin dependencias cíclicas, como en el siguiente código:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { val x = "Hello" }
Y
El acceso Y
ahora arrojará una excepción de puntero nulo, porque x
aún no se ha inicializado. Lo siguiente, sin embargo, funciona bien:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { lazy val x = "Hello" }
Y
EDITAR: lo siguiente también funcionará:
object Y extends { val x = "Hello" } with X
Esto se llama un "inicializador temprano". Vea esta pregunta SO para más detalles.
Una demostración de lazy
- como se definió anteriormente - ejecución cuando se define vs ejecución cuando se accede: (usando 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