`дефа` против `Вэл` против `ленивый Валь оценки в Scala
правильно ли я понимаю, что
def
оценивается каждый раз, когда он получает доступ кlazy val
оценивается как только он получает доступval
оценивается как только он попадает в сферу исполнения?
8 ответов:
Да, хотя для 3-го я бы сказал "когда это заявление выполняется", потому что, например:
def foo() { new { val a: Any = sys.error("b is " + b) val b: Any = sys.error("a is " + a) } }
Это дает
"b is null"
.b
никогда не оценивается и его ошибка никогда не выбрасывается. Но он находится в области, как только контроль входит в блок.
да, но есть один хороший трюк: если у вас есть ленивое значение, и во время первой оценки он получит исключение, в следующий раз, когда вы попытаетесь получить доступ, он попытается переоценить себя.
вот пример:
scala> import io.Source import io.Source scala> class Test { | lazy val foo = Source.fromFile("./bar.txt").getLines | } defined class Test scala> val baz = new Test baz: Test = Test@ea5d87 //right now there is no bar.txt scala> baz.foo java.io.FileNotFoundException: ./bar.txt (No such file or directory) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:137) ... // now I've created empty file named bar.txt // class instance is the same scala> baz.foo res2: Iterator[String] = empty iterator
Я хотел бы объяснить различия через пример, который я выполнил в REPL.Я считаю, что этот простой пример легче понять и объясняет концептуальные различия.
здесь я создаю val result1, lazy val result2 и def result3, каждый из которых имеет строку типа.
A). вал
scala> val result1 = {println("hello val"); "returns val"} hello val result1: String = returns val
здесь println выполняется, потому что значение result1 было вычислено здесь. Итак, теперь result1 всегда будет ссылаться на его значение я.е "возвращает вал".
scala> result1 res0: String = returns val
Итак, теперь вы можете видеть, что result1 теперь относится к его значению. Обратите внимание, что оператор println здесь не выполняется, потому что значение для result1 уже было вычислено, когда оно было выполнено в первый раз. Таким образом, теперь result1 всегда будет возвращать одно и то же значение, и оператор println никогда не будет выполняться снова, потому что вычисление для получения значения result1 уже выполнено.
B). ленивый вал
scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"} result2: String = <lazy>
как мы видим здесь, оператор println не выполняется здесь, и ни одно значение не было вычислено. Такова природа лени.
теперь, когда я ссылаюсь на result2 в первый раз, оператор println будет выполнен и значение будет вычислено и присвоено.
scala> result2 hello lazy val res1: String = returns lazy val
теперь, когда я снова ссылаюсь на result2, на этот раз мы увидим только значение, которое оно содержит, и оператор println не будет выполнен. Отныне, result2 будет просто вести себя как val и возвращать его кэшированное значение все время.
scala> result2 res2: String = returns lazy val
C). def
в случае def, результат должен быть вычислен каждый раз, когда result3 вызывается. Это также основная причина, по которой мы определяем методы как def в scala, потому что методы должны вычислять и возвращать значение каждый раз, когда оно вызывается внутри программы.
scala> def result3 = {println("hello def"); "returns def"} result3: String scala> result3 hello def res3: String = returns def scala> result3 hello def res4: String = returns def
одна веская причина для выбора
def
overval
, особенно в абстрактных классах (или в чертах, которые используются для имитации интерфейсов Java), заключается в том, что вы можете переопределить adef
сval
в подклассах, но не наоборот.о
lazy
, есть две вещи, которые я вижу, что нужно иметь в виду. Первый заключается в том, чтоlazy
вводит некоторые накладные расходы во время выполнения, но я думаю, что вам нужно будет проверить вашу конкретную ситуацию, чтобы узнать, есть ли это на самом деле оказывает значительное влияние на производительность среды выполнения. Другая проблема сlazy
возможно, это задерживает создание исключения, что может затруднить рассуждение о вашей программе, потому что исключение не создается заранее, а только при первом использовании.
вы правы. Для доказательства от спецификация:
из "3.3.1 типы методов" (для
def
):методы без параметров именуют выражения, которые каждый раз пересчитываются на имя метода без параметров делается ссылка.
из "4.1 объявления и определения значений":
определение стоимости
val x : T = e
определяетx
как имя значения, которое является результатом оценкаe
.определение ленивого значения оценивает его правую сторону
e
первый время доступа к значению.
def
определяет метод. Когда вы вызываете метод, метод конечно работает.
val
определяет значение (неизменяемая переменная). Выражение присваивания вычисляется при инициализации значения.
lazy val
определяет значение задержки инициализации. Он будет инициализирован при первом использовании, поэтому выражение присваивания будет вычислено тогда.
имя, определенное def, вычисляется путем замены имени и его выражения RHS каждый раз, когда имя появляется в программе. Поэтому эта замена будет выполняться каждый раз, когда имя появляется в вашей программе.
имя, квалифицированное val, вычисляется сразу же, когда элемент управления достигает своего выражения RHS. Поэтому каждый раз, когда имя появляется в выражении, оно будет рассматриваться как значение этой оценки.
имя, квалифицированное ленивым валом, следует та же политика, что и у квалификации val, за исключением того, что ее RHS будет оцениваться только тогда, когда элемент управления попадает в точку, где имя используется в первый раз
следует указать на потенциальную ловушку в отношении использования val при работе со значениями, неизвестными до времени выполнения.
Возьмем, например,
request: HttpServletRequest
Если бы вы сказали:
val foo = request accepts "foo"
вы получите исключение нулевого указателя как на этапе инициализации вал, запрос не имеет foo (будет только знать во время выполнения).
Итак, в зависимости от расхода доступа / расчета, def или lazy val тогда подходят выбор для значений, определяемых временем выполнения; это или val, который сам является анонимной функцией, которая извлекает данные времени выполнения (хотя последний кажется немного более краевым случаем)