`def` против` val` против `lazy val` в Scala - PullRequest
62 голосов
/ 26 февраля 2012

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

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

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

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

Ответы [ 8 ]

88 голосов
/ 26 февраля 2012

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

Вот пример:

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
49 голосов
/ 26 февраля 2012

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

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

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

26 голосов
/ 03 января 2016

Я хотел бы объяснить различия с помощью примера, который я выполнил в REPL. Я считаю, что этот простой пример легче понять и объяснить концептуальные различия.

Здесь я создаю val result1, alazy val result2 и def result3, каждый из которых имеет тип String.

A).val

scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val

Здесь println выполняется, потому что здесь вычислено значение result1.Итак, теперь result1 всегда будет ссылаться на свое значение, то есть «возвращает val».

scala> result1
res0: String = returns val

Итак, теперь вы можете видеть, что result1 теперь ссылается на свое значение.Обратите внимание, что оператор println здесь не выполняется, потому что значение для result1 уже было вычислено, когда оно было выполнено в первый раз.Итак, теперь, result1 всегда будет возвращать одно и то же значение, и оператор println больше никогда не будет выполняться, потому что вычисление для получения значения result1 уже выполнено.

B).lazy val

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
11 голосов
/ 08 ноября 2012

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

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

6 голосов
/ 26 февраля 2012

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

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

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

Из «4.1. Объявления и определения значений»:

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

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

3 голосов
/ 26 февраля 2012

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

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

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

2 голосов
/ 28 октября 2014

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

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

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

1 голос
/ 10 апреля 2012

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

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

Если вы скажете:

val foo = request accepts "foo"

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

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

...