Что делает ленивый вал?
Я заметил, что 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
) 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)
//StackruException
Но с ленивым вальсом работает нормально
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', но не старое.
Надеюсь это поможет.
Ленивый вал легче всего понять как " запоминание (без аргументов)".
Как и def, lazy val не оценивается, пока не будет вызван. Но результат сохраняется, так что последующие вызовы возвращают сохраненное значение. Запомненный результат занимает место в вашей структуре данных, как val.
Как уже упоминали другие, варианты использования для отложенного вычисления состоят в том, чтобы отложить дорогостоящие вычисления до тех пор, пока они не потребуются, и сохранить их результаты, а также решить некоторые циклические зависимости между значениями.
Ленивые vals фактически реализованы более или менее как запомненные определения. Подробнее об их реализации вы можете прочитать здесь:
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
РЕДАКТИРОВАТЬ: следующие также будут работать:
object Y extends { val x = "Hello" } with X
Это называется "ранний инициализатор". Посмотрите этот ТАК вопрос для более подробной информации.
Демонстрация 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 StackruError
scala> t
java.lang.StackruError
...
// 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
- Все значения инициализируются при строительстве объекта.
- Используйте ленивое ключевое слово, чтобы отложить инициализацию до первого использования
- Внимание: Ленивые значения не являются окончательными и, следовательно, могут показывать недостатки производительности