Плюсы и минусы выбора def над val - PullRequest
11 голосов
/ 09 ноября 2010

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

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 с?

Ответы [ 5 ]

6 голосов
/ 09 ноября 2010

1)

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

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

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

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

2)

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

3)

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

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

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

3 голосов
/ 09 ноября 2010

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

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

2 голосов
/ 09 ноября 2010

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

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

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

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

Возможномогут быть случаи, когда это важно или просто удобно.

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

0 голосов
/ 09 ноября 2010

В вашем примере я бы использовал 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(...) }
0 голосов
/ 09 ноября 2010

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

...