2011-09-20 17 views
193

Zauważyłem, że Scala zapewnia lazy vals. Ale nie rozumiem, co robią.Co robi leniwy 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 

REPL pokazuje, że y jest lazy val, ale czym różni się od normalnego val?

Odpowiedz

267

Różnica między nimi polega na tym, że val jest wykonywany, gdy jest zdefiniowany, podczas gdy lazy val jest wykonywany, gdy uzyskuje się do niego dostęp 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 (zdefiniowanego z def) A lazy val jest wykonywany raz i nigdy więcej. Może to być przydatne, gdy operacja zajmuje dużo czasu i kiedy nie ma pewności, czy jest ona później używana.

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 

Tutaj, gdy wartości x i y nigdy nie są wykorzystywane jedynie x niepotrzebnego marnowania zasobów. Jeśli przyjmiemy, że y nie ma skutków ubocznych i nie wiemy, jak często jest on dostępny (nigdy, raz, tysiące razy), nie ma sensu deklarować go jako def, ponieważ nie chcemy go wykonywać kilka razy.

Aby uzyskać informacje na temat implementacji lazy vals, patrz: question.

+55

Jako uzupełnienie: @ViktorKlang opublikowane na Twitterze: ["mało znany fakt Scala: jeśli inicjalizacja leniwy val rzuca exce ption, spróbuje ponownie zainicjować wartość val przy następnym dostępie. "] (https://twitter.com/#!/viktorklang/status/104483846002704384) –

51

Funkcja ta pomaga nie tylko opóźniać kosztowne obliczenia, ale jest również przydatna do konstruowania wzajemnie zależnych lub cyklicznych struktur. Na przykład. to prowadzi 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 z 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() 
+0

Ale doprowadzi to do tego samego wyjątku StackOverflowException, jeśli twoja metoda toString wyprowadza" foo " atrybut. Dobry przykład "leniwego" w każdym razie !!! –

19

także lazy jest użyteczna bez zależności cyklicznych, 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 rzuci teraz wyjątek wskaźnika pustego, ponieważ x nie został jeszcze zainicjowany. Poniższa jednak działa dobrze:

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

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

EDIT: dodaje się będą również pracować:

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

ten nazywany jest "wczesne inicjator". Aby uzyskać więcej informacji, patrz this SO question.

+11

Czy możesz wyjaśnić, dlaczego deklaracja Y nie inicjuje bezpośrednio zmiennej "x" w pierwszym przykładzie przed wywołaniem konstruktora macierzystego? – Ashoat

+2

Ponieważ konstruktor nadklasy jest pierwszym, który jest wywoływany niejawnie. –

+0

@Ashoat Zobacz [ten link] (https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md), aby uzyskać wyjaśnienie, dlaczego nie został zainicjowany. – Jus12

21

Leniwa wartość val jest najłatwiejsza do zrozumienia jako "memoized def".

Podobnie jak def, leniwy val nie jest oceniany, dopóki nie zostanie wywołany. Ale wynik jest zapisywany, więc kolejne wywołania zwracają zapisaną wartość. Zapamiętywany wynik zajmuje miejsce w strukturze danych, podobnie jak val.

Jak już wspomnieli inni, przypadki użycia leniwego valu mają na celu odłożenie kosztownych obliczeń do momentu, gdy będą potrzebne i zapisanie ich wyników oraz rozwiązanie pewnych zależności między wartościami.

Leniwe szny są w rzeczywistości zaimplementowane mniej więcej jako zapamiętane defs. można przeczytać o szczegółach ich realizacji tutaj:

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

23

Rozumiem, że odpowiedź jest podana ale napisałem prosty przykład, aby to łatwe do zrozumienia dla początkujących jak ja:

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

wyjściowy powyżej kod jest:

x 
----- 
y 
y is: 18 

Jak można zauważyć, x jest drukowana, gdy jest inicjowany, a Y nie jest drukowana, gdy jest ini tializowane w ten sam sposób (brałem tutaj x jako var celowo - aby wyjaśnić, kiedy y zostanie zainicjalizowany). Następnie, gdy wywoływane jest y, jest ono inicjowane, a wartość ostatniego "x" jest brana pod uwagę, ale nie stara.

Mam nadzieję, że to pomoże.

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

scala> lazyEight 
I am lazy ! 
res1: Int = 8 
  • Wszystkie vals inicjowania przy konstrukcji obiektu
  • Używaj lazy kluczowe odroczenia inicjację do pierwszego użycia
  • Uwaga lazy vals nie jest ostateczny i dlatego może pokazać skuteczność minusy