Val-mutable по сравнению с var-immutable в Scala

Есть ли какие-либо рекомендации в Scala о том, когда использовать val с изменяемой коллекцией по сравнению с использованием var с неизменяемой коллекцией? Или вы действительно должны стремиться к val с неизменной коллекцией?

Тот факт, что есть оба типа коллекции, дает мне большой выбор, и часто я не знаете, как сделать этот выбор.

Ответ 1

Довольно общий вопрос, этот. Трудно найти дубликаты.

Вы должны стремиться к ссылочной прозрачности. Это означает, что, если у меня есть выражение "e", я мог бы сделать val x = e и заменить e на x. Это свойство, что изменчивость ломается. Всякий раз, когда вам нужно принять конструктивное решение, максимизируйте для ссылочной прозрачности.

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

С другой стороны, изменчивый набор может сбежать, даже если это не так. При изменении кода вы можете передать его другим методам или вернуть его. Это то, что нарушает ссылочную прозрачность.

На объекте (поле) происходит почти то же самое, но с более тяжелыми последствиями. В любом случае объект будет иметь состояние и, следовательно, нарушит ссылочную прозрачность. Но наличие изменчивой коллекции означает, что даже сам объект может потерять контроль над тем, кто его меняет.

Ответ 2

Если вы работаете с неизменяемыми коллекциями, и вам нужно "модифицировать" их, например, добавлять в них элементы в цикле, тогда вы должны использовать var, потому что вам нужно где-то хранить результирующую коллекцию. Если вы читаете только неизменные коллекции, используйте val s.

В общем, убедитесь, что вы не путаете ссылки и объекты. val являются неизменяемыми ссылками (постоянными указателями в C). То есть, когда вы используете val x = new MutableFoo(), вы сможете изменить объект, на который указывает x, но вы не сможете изменить тот объект x. Противоположно, если вы используете var x = new ImmutableFoo(). Подбирая мой первоначальный совет: если вам не нужно менять, к какому объекту относятся контрольные точки, используйте val s.

Ответ 3

Лучший способ ответить на этот вопрос - пример. Предположим, что мы каким-то образом просто собираем номера по какой-то причине. Мы хотим зарегистрировать эти числа и отправим коллекцию в другой процесс, чтобы сделать это.

Конечно, мы все еще собираем номера после отправки коллекции в журнал. И пусть говорят, что в процессе регистрации есть некоторые накладные расходы, которые задерживают фактическое ведение журнала. Надеюсь, вы увидите, что происходит.

Если мы сохраним эту коллекцию в mutable val, (изменяемый, потому что мы постоянно добавляем к ней), это означает, что процесс, выполняющий ведение журнала, будет искать тот же объект, который все еще обновляется процессом нашей коллекции. Эта коллекция может быть обновлена ​​в любое время, и поэтому, когда пришло время регистрации, мы не можем фактически регистрировать отправленную вами коллекцию.

Если мы используем неизменяемый var, мы отправляем неизменяемую структуру данных в регистратор. Когда мы добавим больше номеров в нашу коллекцию, мы заменим нашу var новой неизменной структурой данных. Это не означает, что коллекция, отправленная в журнал, заменяется! Он все еще ссылается на сбор, который он отправил. Поэтому наш регистратор действительно зарегистрирует полученную коллекцию.

Ответ 4

Я думаю, что примеры в этом сообщении в блоге прольют больше света, поскольку вопрос о том, какая комбинация для использования становится еще более важной в сценариях concurrency: важность неизменяемости для concurrency. И пока мы на нем, обратите внимание на предпочтительное использование синхронизированного vs @volatile против чего-то вроде AtomicReference: три инструмента

Ответ 5

var immutable vs. val mutable

В дополнение к многим отличным ответам на этот вопрос. Вот простой пример, иллюстрирующий потенциальную опасность val mutable:

Переменные объекты могут быть изменены внутри методов, которые принимают их как параметры, а переназначение не допускается.

import scala.collection.mutable.ArrayBuffer

object MyObject {
    def main(args: Array[String]) {

        val a = ArrayBuffer(1,2,3,4)
        silly(a)
        println(a) // a has been modified here
    }

    def silly(a: ArrayBuffer[Int]): Unit = {
        a += 10
        println(s"length: ${a.length}")
    }
}

Результат:

length: 5
ArrayBuffer(1, 2, 3, 4, 10)

Что-то вроде этого не может произойти с var immutable, потому что переназначение не разрешено:

object MyObject {
    def main(args: Array[String]) {
        var v = Vector(1,2,3,4)
        silly(v)
        println(v)
    }

    def silly(v: Vector[Int]): Unit = {
        v = v :+ 10 // This line is not valid
        println(s"length of v: ${v.length}")
    }
}

Результаты в:

error: reassignment to val

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