Плюсы и минусы выбора 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(...) }