Respostas:
A diferença entre eles é que a val
é executado quando definido, enquanto a lazy val
é executado quando acessado pela primeira 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
Ao contrário de um método (definido com def
), a lazy val
é executado uma vez e nunca mais. Isso pode ser útil quando uma operação leva muito tempo para ser concluída e quando não se tem certeza se será usada posteriormente.
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
Aqui, quando os valores x
e y
nunca são usados, apenas x
desperdiçando recursos desnecessariamente. Se supusermos que y
isso não tem efeitos colaterais e que não sabemos com que frequência ele é acessado (nunca, uma vez, milhares de vezes), é inútil declará-lo, def
pois não queremos executá-lo várias vezes.
Se você quiser saber como lazy vals
são implementadas, consulte esta pergunta .
Lazy<T>
.NET
Esse recurso ajuda não apenas a atrasar cálculos caros, mas também é útil para construir estruturas dependentes ou cíclicas mútuas. Por exemplo, isso leva a um estouro de pilha:
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
Mas com vals preguiçosos, funciona bem
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()
Entendo que a resposta foi dada, mas escrevi um exemplo simples para facilitar o entendimento de iniciantes como eu:
var x = { println("x"); 15 }
lazy val y = { println("y"); x+1 }
println("-----")
x = 17
println("y is: " + y)
A saída do código acima é:
x
-----
y
y is: 18
Como pode ser visto, x é impresso quando inicializado, mas y não é impresso quando inicializado da mesma maneira (tomei x como var intencionalmente aqui - para explicar quando y é inicializado). Em seguida, quando y é chamado, é inicializado, assim como o valor do último 'x' é levado em consideração, mas não o antigo.
Espero que isto ajude.
Um val preguiçoso é mais facilmente entendido como um " def memorizado (sem argumento)".
Como um def, um val preguiçoso não é avaliado até ser invocado. Mas o resultado é salvo para que as chamadas subseqüentes retornem o valor salvo. O resultado memorizado ocupa espaço em sua estrutura de dados, como um valor.
Como outros já mencionaram, os casos de uso de um val lento são adiar cálculos caros até que sejam necessários e armazenar seus resultados e resolver certas dependências circulares entre valores.
Vals preguiçosos são de fato implementados mais ou menos como padrões memorizados. Você pode ler sobre os detalhes de sua implementação aqui:
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
Também lazy
é útil sem dependências cíclicas, como no código a seguir:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { val x = "Hello" }
Y
O acesso Y
agora lançará uma exceção de ponteiro nulo, porque x
ainda não foi inicializado. O seguinte, no entanto, funciona bem:
abstract class X {
val x: String
println ("x is "+x.length)
}
object Y extends X { lazy val x = "Hello" }
Y
EDIT: o seguinte também funcionará:
object Y extends { val x = "Hello" } with X
Isso é chamado de "inicializador inicial". Veja esta pergunta para mais detalhes.
Uma demonstração de lazy
- como definido acima - execução quando definida vs execução quando acessada: (usando o shell 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