Подтвердить что ты не робот

`def` vs` val` vs `lazy val` в Scala

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

  • def оценивается каждый раз при обращении к нему

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

  • val оценивается после того, как он попадает в область выполнения?

4b9b3361

Ответ 1

Да, хотя для третьего я бы сказал "когда этот оператор выполняется", потому что, например:

def foo() {
    new {
        val a: Any = sys.error("b is " + b)
        val b: Any = sys.error("a is " + a)
    }
}

Это дает "b is null". b никогда не оценивается, и его ошибка никогда не возникает. Но он находится в области действия, как только элемент управления входит в блок.

Ответ 2

Да, но есть один хороший трюк: если у вас есть ленивое значение, и во время первой оценки он получит исключение, в следующий раз, когда вы попытаетесь получить к нему доступ, он попытается переоценить себя.

Вот пример:

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

Ответ 3

Я хотел бы объяснить различия в примере, который я выполнил в 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

Ответ 4

Одна из веских причин выбора def over val, особенно в абстрактных классах (или в чертах, которые используются для имитации интерфейсов Java), заключается в том, что вы можете переопределить def с val в подклассах, но не наоборот.

Что касается lazy, есть две вещи, которые я могу видеть, что нужно иметь в виду. Во-первых, lazy вводит некоторые служебные данные во время выполнения, но я полагаю, что вам нужно будет проверить вашу конкретную ситуацию, чтобы выяснить, действительно ли это оказывает существенное влияние на производительность среды выполнения. Другая проблема с lazy заключается в том, что она может задерживать создание исключения, что может затруднить рассуждение о вашей программе, поскольку исключение не создается заранее, а только при первом использовании.

Ответ 5

Вы правы. Для подтверждения спецификации:

Из "3.3.1 Типы методов" (для def):

Параметрические методы называют выражения, которые пересматриваются каждый раз ссылается на имя параметра без параметров.

Из "4.1 Декларации и определения значений":

Определение значения val x : T = e определяет x как имя значения, которое получается из оценка e.

Определение ленивого значения оценивает его правую сторону e первый время, к которому обращается доступ.

Ответ 6

def определяет метод. Когда вы вызываете метод, выполняется метод ofcourse.

val определяет значение (неизменяемую переменную). Выражение присваивания вычисляется при инициализации значения.

lazy val определяет значение с задержкой инициализации. Он будет инициализирован при первом использовании, поэтому выражение назначения будет оценено тогда.

Ответ 7

Имя, присвоенное def, оценивается путем замены имени и его выражения RHS каждый раз, когда имя появляется в программе. Поэтому эта замена будет выполняться каждый раз, когда имя появляется в вашей программе.

Имя, присвоенное val, оценивается немедленно, когда управление достигает своего выражения RHS. Поэтому каждый раз, когда имя появляется в выражении, оно будет рассматриваться как значение этой оценки.

Имя, присвоенное lazy val, следует той же политике, что и квалификация val, за исключением того, что ее RHS будет оцениваться только тогда, когда элемент управления попадает в точку, где имя используется в первый раз

Ответ 8

Следует указать потенциальную ошибку при использовании значения val при работе со значениями, неизвестными до времени выполнения.

Возьмем, например, request: HttpServletRequest

Если бы вы сказали:

val foo = request accepts "foo"

Вы получите исключение нулевого указателя, как в момент инициализации val, запрос не имеет foo (будет известен только во время выполнения).

Таким образом, в зависимости от расхода доступа/вычисления def или lazy val являются подходящими выборами для значений, определенных во время выполнения; это или val, который сам является анонимной функцией, которая извлекает данные времени выполнения (хотя последнее кажется немного более кратным)