Что делает ленивый вал?

я заметил, что Scala обеспечивают lazy vals. Но я не понимаю, что они делают.

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 видно, что y это lazy val, но чем он отличается от нормального val?

6 ответов


разница между ними в том, что val выполняется, когда он определен, тогда как lazy val выполняется при первом доступе к нему.

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

В отличие от метода (определяется с def) a lazy val выполняется один раз, а затем больше никогда. Это может быть полезно, когда операция занимает много времени для завершения и когда он не уверен, если он используется позже.

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

здесь, когда значения x и y никогда не используются, только x ненужная трата ресурсов. Если предположить, что y не имеет побочных эффектов и что мы не знаем, как часто к нему обращаются (никогда, один раз, тысячи раз) бесполезно объявлять его как def поскольку мы не хотим выполнять его несколько раз.

если вы хотите знать, как lazy vals реализованы, см. Это вопрос.


эта функция помогает не только задерживать дорогостоящие вычисления, но также полезна для построения взаимозависимых или циклических структур. Е. Г. это приводит к переполнению стека:

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

но с lazy vals он отлично работает

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()

Я понимаю, что ответ дан, но я написал простой пример, чтобы сделать его легко понять, для новичков вроде меня:

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

вывод вышеуказанного кода:

x
-----
y
y is: 18

Как видно, x печатается, когда он инициализирован, но y не печатается, когда он инициализирован таким же образом (я взял x как VAR намеренно здесь - чтобы объяснить, когда y инициализируется). Далее, когда вызывается y, он инициализируется, а также учитывается значение последнего " x но не старый.

надеюсь, что это помогает.


ленивый вал наиболее легко понять как "memoized (no-arg) def".

Как и def, ленивый val не оценивается, пока он не будет вызван. Но результат сохраняется, так что последующие вызовы возвращают сохраненное значение. Memoized результат занимает место в вашей структуре данных, как val.

Как уже упоминалось, варианты использования для ленивого val-отложить дорогостоящие вычисления до тех пор, пока они не понадобятся и не сохранят свои результаты, а также решить некоторые циклические зависимости между значениями.

Lazy vals фактически реализованы более или менее как memoized defs. Подробнее об их реализации вы можете прочитать здесь:

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


и lazy полезно без циклических зависимостей, как показано в следующем коде:

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

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

ссылке Y теперь будет выдавать исключение нулевого указателя, потому что x еще не инициализирован. Однако работает следующее:

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

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

EDIT: также будет работать следующее:

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

это называется "ранний инициализатор". См.это так вопрос для получения более подробной информации.


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

scala> lazyEight
I am lazy !
res1: Int = 8
  • все val инициализируются во время строительства объекта
  • используйте ключевое слово lazy, чтобы отложить инициализацию до первого использования
  • внимание: Lazy vals не являются окончательными и поэтому могут показывать недостатки производительности