Я правильно понимаю, что
-
def
оценивается каждый раз при обращении к нему -
lazy val
оценивается после его доступа -
val
оценивается после того, как он попадает в область выполнения?
Я правильно понимаю, что
def
оценивается каждый раз при обращении к нему
lazy val
оценивается после его доступа
val
оценивается после того, как он попадает в область выполнения?
Да, хотя для третьего я бы сказал "когда этот оператор выполняется", потому что, например:
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 = [email protected]
//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.I считаю, что этот простой пример легче понять и объяснить концептуальные различия.
Здесь я создаю val result1, ленивый результат val2 и def def3, каждый из которых имеет тип String.
А). Вэл
scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val
Здесь println выполняется, потому что здесь вычисляется значение result1. Итак, теперь result1 всегда будет ссылаться на его значение i.e "возвращает val".
scala> result1
res0: String = returns val
Итак, теперь вы можете видеть, что result1 теперь ссылается на его значение. Обратите внимание, что инструкция println здесь не выполняется, потому что значение для result1 уже было вычислено, когда оно было выполнено в первый раз. Итак, теперь и result1 всегда будет возвращать одно и то же значение, и оператор println никогда не будет выполнен снова, потому что вычисление для получения значения result1 уже выполнено.
В). lazy val
scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>
Как мы видим здесь, инструкция println здесь не выполняется, и ни одно значение не было вычислено. Это характер ленивости.
Теперь, когда я впервые ссылаюсь на результат2, будет выполняться инструкция println, и значение будет вычисляться и назначаться.
scala> result2
hello lazy val
res1: String = returns lazy val
Теперь, когда я снова обращусь к результату2, на этот раз мы увидим только его значение, и инструкция println не будет выполнена. С этого момента result2 будет просто вести себя как val и постоянно возвращать кеш-значение.
scala> result2
res2: String = returns lazy val
С). Защита
В случае 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
over val
, особенно в абстрактных классах (или в чертах, которые используются для имитации интерфейсов Java), заключается в том, что вы можете переопределить def
с val
в подклассах, но не наоборот.
Что касается lazy
, есть две вещи, которые я могу видеть, что нужно иметь в виду. Во-первых, lazy
вводит некоторые служебные данные во время выполнения, но я полагаю, что вам нужно будет проверить вашу конкретную ситуацию, чтобы выяснить, действительно ли это оказывает существенное влияние на производительность среды выполнения. Другая проблема с lazy
заключается в том, что она может задерживать создание исключения, что может затруднить рассуждение о вашей программе, поскольку исключение не создается заранее, а только при первом использовании.
Вы правы. Для подтверждения спецификации:
Из "3.3.1 Типы методов" (для def
):
Параметрические методы называют выражения, которые пересматриваются каждый раз ссылается на имя параметра без параметров.
Из "4.1 Декларации и определения значений":
Определение значения
val x : T = e
определяетx
как имя значения, которое получается из оценкаe
.Определение ленивого значения оценивает его правую сторону
e
первый время, к которому обращается доступ.
def
определяет метод. Когда вы вызываете метод, выполняется метод ofcourse.
val
определяет значение (неизменяемую переменную). Выражение присваивания вычисляется при инициализации значения.
lazy val
определяет значение с задержкой инициализации. Он будет инициализирован при первом использовании, поэтому выражение назначения будет оценено тогда.
Имя, присвоенное def, оценивается путем замены имени и его выражения RHS каждый раз, когда имя появляется в программе. Поэтому эта замена будет выполняться каждый раз, когда имя появляется в вашей программе.
Имя, присвоенное val, оценивается немедленно, когда управление достигает своего выражения RHS. Поэтому каждый раз, когда имя появляется в выражении, оно будет рассматриваться как значение этой оценки.
Имя, присвоенное lazy val, следует той же политике, что и квалификация val, за исключением того, что ее RHS будет оцениваться только тогда, когда элемент управления попадает в точку, где имя используется в первый раз
Следует указать потенциальную ошибку при использовании значения val при работе со значениями, неизвестными до времени выполнения.
Возьмем, например, request: HttpServletRequest
Если бы вы сказали:
val foo = request accepts "foo"
Вы получите исключение нулевого указателя, как в момент инициализации val, запрос не имеет foo (будет известен только во время выполнения).
Таким образом, в зависимости от расхода доступа/вычисления def или lazy val являются подходящими выборами для значений, определенных во время выполнения; это или val, который сам является анонимной функцией, которая извлекает данные времени выполнения (хотя последнее кажется немного более кратным)