2011-09-20 37 views
193

Noté que Scala proporciona lazy vals. Pero no entiendo lo que hacen.¿Qué hace un vago val?

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 

El REPL muestra que y es una lazy val, pero ¿cómo se diferencia de una normal de val?

Respuesta

267

La diferencia entre ellos es que un val se ejecuta cuando se define, mientras que un 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 

En contraste con un método (que se define con def) un lazy val se ejecuta una vez y luego nunca más. Esto puede ser útil cuando una operación tarda mucho tiempo en completarse y cuando no se sabe con seguridad 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 = [email protected] // we have to wait two seconds to the result 

scala> new Y 
res6: Y = [email protected] // this appears immediately 

Aquí, cuando no se utilizan los valores x y y, sólo x el desperdicio de 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 como def ya que no queremos ejecutarlo varias veces.

Si desea saber cómo se implementan lazy vals, consulte este question.

+55

Como complemento: @ViktorKlang publicado en Twitter: ["Poco conocido hecho de Scala: si la inicialización de un val perezoso arroja un exce ption, intentará reinicializar el val en el siguiente acceso. "] (https://twitter.com/#!/viktorklang/status/104483846002704384) –

51

Esta característica ayuda no solo a retrasar los costosos cálculos, sino que también es útil para construir estructuras dependientes o cíclicas mutuas. P.ej. esto conduce 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 que 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() 
+0

Pero dará lugar a la misma StackOverflowException si su método toString arroja" foo " atributo. Buen ejemplo de "perezoso" de todos modos! –

19

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 

Acceso Y arrojará ahora una excepción de puntero nulo, porque x aún no se ha inicializado. A continuación, sin embargo, funciona bien:

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

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

EDIT: Lo siguiente también funcionará:

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

Esto se llama una "inicializador temprana". Ver this SO question para más detalles.

+11

¿Puedes aclarar por qué la declaración de Y no inicializa inmediatamente la variable "x" en el primer ejemplo antes de llamar al constructor padre? – Ashoat

+2

Porque el constructor de superclase es el primero que recibe una llamada implícita. –

+0

@Ashoat Consulte [este enlace] (https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md) para obtener una explicación de por qué no se inicializa. – Jus12

21

Un vago val se entiende más fácilmente como "memoized def".

Como una definición, un valor diferido no se evalúa hasta que se invoca. Pero el resultado se guarda para que las invocaciones posteriores devuelvan el valor guardado. El resultado memoial ocupa espacio en su estructura de datos, como un valor.

Como han mencionado otros, los casos de uso de un valor diferido son diferir costosos cálculos hasta que se necesiten y almacenar sus resultados, y resolver ciertas dependencias circulares entre valores.

Lazy vals de hecho se implementan más o menos como defs memorados. Usted puede leer acerca de los detalles de su aplicación aquí:

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

23

entiendo que se le da la respuesta, pero me escribió un ejemplo sencillo para que sea fácil de entender para los principiantes como yo:

var x = { println("x"); 15 } 
lazy val y = { println("y"); x+1 } 
println("-----") 
x = 17 
println("y is: " + y) 

salida del código anterior es:

x 
----- 
y 
y is: 18 

Como se puede observar, x se imprime cuando se ha inicializado, pero no se imprime y cuando es ini tializado de la misma manera (He tomado x como var intencionalmente aquí - para explicar cuándo y se inicializa). Luego cuando se invoca a y, se inicializa y se toma en cuenta el valor de la última 'x', pero no el anterior.

Espero que esto ayude.

0
scala> lazy val lazyEight = { 
    | println("I am lazy !") 
    | 8 
    | } 
lazyEight: Int = <lazy> 

scala> lazyEight 
I am lazy ! 
res1: Int = 8 
  • Todos Vals se inicializan durante la construcción objeto
  • uso de palabras clave perezoso para aplazar la inicialización hasta el primer uso
  • Atención: Vals perezosos no son definitivos y por lo tanto podría mostrar un rendimiento inconvenientes