Изменяемая переменная против изменяемой коллекции

В моем приложении мне нужна возможность менять местами элементы в коллекции. Итак, у меня есть выбор:

  1. использовать изменяемую коллекцию, объявленную как val
  2. или используйте неизменяемую коллекцию, объявленную как var (и всегда переназначайте новую коллекцию на var)

Но в Scala (в функциональном программировании) изменчивости всегда избегают. Так что менее ужасно: изменяемая коллекция, объявленная с использованием val, или неизменяемая коллекция, объявленная как var?


person MyTitle    schedule 23.07.2013    source источник
comment
Без дополнительного контекста это не имеет значения. Нет и функционального подхода. Изменяемая коллекция, вероятно, будет работать лучше, чем неизменяемая переменная.   -  person dbyrne    schedule 23.07.2013
comment
@dbyrne Изменяемая коллекция, вероятно, будет работать лучше, чем неизменяемая var whoa? разве эти двое не играют в одной команде? например val misterMutableCollection = collection.mutable....   -  person om-nom-nom    schedule 23.07.2013
comment
@ om-nom-nom Используя неизменяемую переменную, вам нужно будет создать совершенно новую коллекцию и поменять ее местами. С изменяемым val вы можете использовать исходную коллекцию и методы вызова для ее изменения.   -  person dbyrne    schedule 23.07.2013


Ответы (6)


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

import collection.mutable.ArrayBuffer
val a = ArrayBuffer(1,2,3,4)
val i = a.iterator
i.hasNext             // True
a.reduceToSize(0)
i.next                // Boom!

java.lang.IndexOutOfBoundsException: 0
at scala.collection.mutable.ResizableArray$class.apply(ResizableArray.scala:43)
    ...

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

var v = Vector(1,2,3,4)
val i = v.iterator
i.hasNext            // True
v = Vector[Int]()
i.next               // 1

Однако теперь вы должны передать v в качестве возвращаемого значения из любого метода, который может его изменить (по крайней мере, за пределами класса, который его содержит). Это также может вызвать проблемы, если вы забудете обновить исходное значение:

var v = Vector(1,2,3,4)
def timesTwo = v.map(_ * 2)
timesTwo
v       // Wait, it's still 1,2,3,4?!

Но тогда и это не обновляется, не так ли ?:

a.map(_ * 2)    // Map doesn't update, it produces a new copy!

Итак, как правило,

  1. Производительность требует, чтобы вы использовали один - используйте его
  2. Локальная область видимости внутри метода - используйте изменяемую коллекцию
  3. Совместно с другими потоками / классами - используйте неизменяемую коллекцию
  4. Реализация внутри класса, простой код - используйте изменяемую коллекцию
  5. Реализация внутри класса, сложный код - используйте неизменяемый

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

person Rex Kerr    schedule 23.07.2013

Если вы используете var, содержащий неизменяемую коллекцию, вы можете публиковать ее довольно свободно (хотя вы можете пометить var как @volatile). В любой момент времени другой код может получить только конкретный снимок этого состояния, которое никогда не может измениться.

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

person Randall Schulz    schedule 23.07.2013
comment
Не могли бы вы поделиться информацией о том, как @volatile работает в Scala? Насколько я понимаю, это указывает на то, что несколько потоков могут обращаться к нему одновременно, но я не уверен, что эта аннотация действительно меняет или делает с переменной? Благодарность - person LaloInDublin; 23.07.2013
comment
@volatile работает точно так же, как ключевое слово volatile в java. Работает на уровне ВМ. См. stackoverflow.com/questions/2694439/ Например. - person Christophe Vanfleteren; 23.07.2013

Изменчивость - это чудовище, которого лучше держать в клетке. В идеале, в хорошо протестированном и часто используемом типе клетки вроде scala mutable collection.

person om-nom-nom    schedule 23.07.2013

Один безопасный метод, который я использовал, работает примерно так ... сначала скройте свой var, чтобы сделать его потокобезопасным, например:

private[this] val myList: scala.collection.mutable.(whatever)

private [this] ограничивает переменную не только этим классом, но только этим конкретным экземпляром. Никакая другая переменная не может получить к нему доступ.

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

def myListSafe = myList.toList (or some other safe conversion function like toMap, etc)

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

person LaloInDublin    schedule 23.07.2013

Переназначение var может быть трудно читать / поддерживать, особенно если это происходит, например, в нескольких местах в методе.

Таким образом, с val вы получаете по крайней мере неизменную ссылку.

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

Как правильно указал dbyrne: если невозможно преобразовать изменяемую коллекцию в неизменяемую (например, если вы реализуете кеш), а изменяемая коллекция является частью общедоступного API, тогда var может быть лучше. Другой вариант в этом случае - создать неизменяемую копию в методе, реализующем общедоступный API.

person Beryllium    schedule 23.07.2013
comment
На мой взгляд, это пока лучший ответ, но есть случаи, когда важнее, чтобы сама коллекция была неизменной, а ссылка не так важна. Если вы являетесь автором библиотеки и хотите убедиться, что кто-либо, использующий ваш API, не изменяет коллекцию после того, как вы вернете ее им, вам подойдет неизменяемая переменная. - person dbyrne; 23.07.2013

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

@volatile
private var myList = immutable.List.empty[Foo]
def theList = myList
person sourcedelica    schedule 23.07.2013