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

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

231 голосов
/ 20 сентября 2011

Я заметил, что 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?

Ответы [ 7 ]

315 голосов
/ 20 сентября 2011

Разница между ними заключается в том, что 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) 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, посмотрите этот вопрос .

58 голосов
/ 20 сентября 2011

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

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

Но с ленивыми вальсами все нормально работает

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()
39 голосов
/ 08 июня 2016

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

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', но не старое.

Надеюсь, это поможет.

32 голосов
/ 08 апреля 2015

Ленивый val легче всего понять как « памятный (без аргументов) def».

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

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

Ленивые значения фактически реализованы более или менее как запомненные определения.Вы можете прочитать подробности их реализации здесь:

http://docs.scala -lang.org / sips / pending / улучшенный-lazy-val-initialization.html

19 голосов
/ 28 сентября 2011

Также 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 

Это называется «ранним инициализатором».См. этот SO вопрос для более подробной информации.

2 голосов
/ 05 декабря 2018

Демонстрация lazy - как определено выше - выполнение, когда оно определено, против выполнения при доступе: (с использованием оболочки 2.12.7 scala)

// 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
1 голос
/ 11 июля 2017
scala> lazy val lazyEight = {
     |   println("I am lazy !")
     |   8
     | }
lazyEight: Int = <lazy>

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