Плюсы и минусы выбора def над val

Я задаю несколько иной вопрос, чем этот. Предположим, у меня есть фрагмент кода:

def foo(i : Int) : List[String] = {
  val s = i.toString + "!" //using val
  s :: Nil
}

Это функционально эквивалентно следующему:

def foo(i : Int) : List[String] = {
  def s = i.toString + "!" //using def
  s :: Nil
}

Почему я должен выбирать один за другим? Очевидно, я предполагаю, что второй имеет небольшие недостатки:

  • создание более байт-кода (внутренний def поднимается до метода в классе)
  • служебные данные о производительности выполнения при вызове метода для доступа к значению
  • Нестрогая оценка означает, что я мог бы легко получить доступ к s в два раза (т.е. ненужно переделать расчет)

Единственное преимущество, о котором я могу думать, это:

  • нестрогая оценка s означает, что она вызывается только в том случае, если она используется (но тогда я мог бы просто использовать lazy val)

Каковы мысли людей здесь? Есть ли существенная неудобства для меня, делая все внутренние val def s?

Ответ 1

1)

Один из ответов, о которых я не упоминал, - это то, что фрейм стека для описываемого вами метода может быть меньше. Каждый объявленный val будет занимать слот в стеке JVM, однако всякий раз, когда вы используете полученное значение def, оно будет потребляться в первом выражении, в котором вы его используете. Даже если def ссылается на что-то из среда, компилятор пройдет. HotSpot должен оптимизировать обе эти вещи, или, как утверждают некоторые люди. См:

http://www.ibm.com/developerworks/library/j-jtp12214/

Так как внутренний метод скомпилирован в обычный частный метод за сценой, и он, как правило, очень мал, компилятор JIT может выбрать его встроить, а затем оптимизировать. Это может сэкономить время, выделяя меньшие кадры стека (?) Или, имея меньше элементов в стеке, быстрее приведет к локальным переменным.

Но, возьмите это с (большим) зерном соли - я на самом деле не сделал обширных тестов для резервного копирования этого заявления.

2)

Кроме того, чтобы расширить на действительный ответ Кевина, стабильный val также означает, что вы можете использовать его с зависимыми от пути типами - то, что вы не можете сделать с помощью def, поскольку компилятор не проверяет его чистоту.

3)

По другой причине вы можете использовать def, см. связанный с этим вопрос, заданный не так давно:

Функциональная обработка потоков Scala без ошибок OutOfMemory

По существу, использование def для создания Streams гарантирует, что не существует дополнительных ссылок на эти объекты, что важно для GC. Поскольку Stream ленивы в любом случае, накладные расходы на их создание, вероятно, незначительны, даже если у вас есть несколько def s.

Ответ 2

Значение val строгое, оно дает значение, как только вы определяете вещь.

Внутри компилятор будет отмечать его как STABLE, что эквивалентно окончательному в Java. Это должно позволить JVM делать всевозможные оптимизации - я просто не знаю, что это такое:)

Ответ 3

Я вижу преимущество в том, что вы менее привязаны к местоположению при использовании def, чем при использовании val.

Это не техническое преимущество, но в некоторых случаях позволяет лучше структурировать.

Итак, глупый пример (пожалуйста, отредактируйте этот ответ, если у вас есть лучший), это невозможно с помощью val:

def foo(i : Int) : List[String] = {
  def ret = s :: Nil
  def s = i.toString + "!"
  ret
}

Бывают случаи, когда это важно или просто удобно.

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

Ответ 4

Для локального объявления, подобного этому (без аргументов, оцененных ровно один раз и без кода, оцененного между точкой объявления и точкой оценки), нет семантической разницы. Я не удивлюсь, если версия "val" скомпилирована для более простого и более эффективного кода, чем версия "def", но вам нужно будет проверить байт-код и, возможно, профиль, чтобы быть уверенным.

Ответ 5

В вашем примере я бы использовал val. Я думаю, что выбор val/def более значим при объявлении членов класса:

class A { def a0 = "a"; def a1 = "a" }

class B extends A {
  var c = 0
  override def a0 = { c += 1; "a" + c }
  override val a1 = "b" 
}

В базовом классе, использующем def, можно переопределить подкласс, возможно, def, который не возвращает константу. Или он может переопределить с val. Это дает большую гибкость, чем val.

Изменить: еще один пример использования def over val - это когда абстрактный класс имеет "val", для которого значение должно быть предоставлено подклассом.

abstract class C { def f: SomeObject }
new C { val f = new SomeObject(...) }