`дефа` против `Вэл` против `ленивый Валь оценки в Scala


правильно ли я понимаю, что

  • def оценивается каждый раз, когда он получает доступ к

  • lazy val оценивается как только он получает доступ

  • val оценивается как только он попадает в сферу исполнения?

8 56

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 over val, особенно в абстрактных классах (или в чертах, которые используются для имитации интерфейсов Java), заключается в том, что вы можете переопределить a def с 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, который сам является анонимной функцией, которая извлекает данные времени выполнения (хотя последний кажется немного более краевым случаем)