Что делает ленивый вал?
Я заметил, что 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
?
Ответы
Ответ 1
Разница между ними заключается в том, что a 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 = [email protected] // we have to wait two seconds to the result
scala> new Y
res6: Y = [email protected] // this appears immediately
Здесь, когда значения x
и y
никогда не используются, только x
излишне тратит ресурсы. Если мы предположим, что y
не имеет побочных эффектов и что мы не знаем, как часто он обращается (никогда, один раз, тысячи раз), бесполезно объявлять его как def
, так как мы не хотим его выполнять несколько раз.
Если вы хотите знать, как реализованы lazy vals
, см. этот question.
Ответ 2
Эта функция помогает не только откладывать дорогостоящие вычисления, но также полезно создавать взаимно зависимые или циклические структуры. Например. это приводит к переполнению стека:
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()
Ответ 3
Я понимаю, что ответ дан, но я написал простой пример, чтобы понять его для начинающих, таких как я:
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" принимается во внимание, но не является старым.
Надеюсь, что это поможет.
Ответ 4
Ленивый val наиболее легко понимается как " memoized (no-arg) def".
Как def, lazy val не оценивается до его вызова. Но результат сохраняется, так что последующие вызовы возвращают сохраненное значение. Мембранный результат занимает место в вашей структуре данных, например, val.
Как отмечали другие, варианты использования для ленивого val - отложить дорогостоящие вычисления до тех пор, пока они не понадобятся, и сохранить их результаты, а также решить определенные круговые зависимости между значениями.
На самом деле ленивые валы реализованы более или менее как memoized defs. Вы можете прочитать о деталях их реализации здесь:
http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html
Ответ 5
Также 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
ИЗМЕНИТЬ: будет также работать следующее:
object Y extends { val x = "Hello" } with X
Это называется "ранним инициализатором". Подробнее см. этот вопрос SO.
Ответ 6
scala> lazy val lazyEight = {
| println("I am lazy !")
| 8
| }
lazyEight: Int = <lazy>
scala> lazyEight
I am lazy !
res1: Int = 8
- Все vals инициализируются при построении объекта
- Использовать ленивое ключевое слово для отсрочки инициализации до первого использования
- Внимание: Lazy vals не являются окончательными и поэтому могут показывать недостатки производительности.
Ответ 7
Демонстрация 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