В чём разница между immutable и mutable коллекциями?

Разница между immutable и mutable коллекциями в Scala заключается в способности коллекции изменять своё содержимое после создания. Это различие отражает базовые принципы функционального и императивного программирования.

Immutable коллекции

Immutable (неизменяемые) коллекции нельзя изменить после создания. Все операции, которые выглядят как изменения (например, добавление элемента), возвращают новую коллекцию, при этом оригинальная остаётся неизменной.

Примеры:

val list = List(1, 2, 3)
val newList = list :+ 4 // создаётся новый список List(1, 2, 3, 4)

Основные характеристики:

  • Потокобезопасность (thread-safe) без синхронизации.

  • Лучше подходят для параллельных вычислений.

  • Идеальны в функциональном программировании.

  • Объекты не изменяются, можно делиться ими без риска.

Типичные структуры:

  • List, Vector, Map, Set, Range, Stream, Queue, Stack, Seq (из scala.collection.immutable)

  • Значения создаются и работают по принципу «новый объект вместо изменения».

Пример:

val set1 = Set(1, 2, 3)
val set2 = set1 + 4 // set1 остаётся прежним

Mutable коллекции

Mutable (изменяемые) коллекции могут изменяться: можно добавлять, удалять или изменять элементы на месте, без создания новой структуры.

Примеры:

val buffer = scala.collection.mutable.ListBuffer(1, 2, 3)
buffer += 4 // изменяется исходный buffer

Основные характеристики:

  • Требуют осторожности при использовании в многопоточном коде.

  • Часто быстрее в императивных сценариях.

  • Могут быть эффективнее, если нужно выполнять множество модификаций.

Типичные структуры:

  • scala.collection.mutable.ListBuffer, ArrayBuffer, HashMap, HashSet, Queue, Stack, Array, BitSet, LinkedHashMap

Пример:

val map = scala.collection.mutable.Map("a" -> 1)
map("b") = 2 // изменили исходный map

Подключение нужной версии

Scala делит стандартные коллекции на три пространства имён:

Пространство Описание
scala.collection Общее (родительское) пространство
--- ---
scala.collection.immutable Все коллекции по умолчанию неизменяемые
--- ---
scala.collection.mutable Все коллекции изменяемые
--- ---

Можно указать нужную имплементацию:

import scala.collection.mutable.ListBuffer

Сравнение по основным признакам

Признак Immutable коллекции Mutable коллекции
Изменяемость Нет Да
--- --- ---
Потокобезопасность Высокая, не требуют синхронизации Низкая, требуют явной защиты
--- --- ---
Производительность Хорошая при небольшом количестве изменений Эффективнее при множественных изменениях
--- --- ---
Поддержка в Scala По умолчанию используются Требуют явного импорта
--- --- ---
Совместимость с FP Отличная Ограниченная
--- --- ---
Семантика операций Возвращают новые коллекции Модифицируют текущую коллекцию
--- --- ---
Примеры коллекций List, Map, Set, Vector, Range ListBuffer, ArrayBuffer, HashMap, Array
--- --- ---

Когда использовать какую

  • Immutable коллекции подходят для:

    • Функционального кода

    • Больше читаемости и предсказуемости

    • Безопасного совместного использования

    • Параллельной обработки

  • Mutable коллекции подходят для:

    • Императивного, процедурного стиля

    • Быстрой модификации в циклах

    • Временных буферов, алгоритмов на массивах

Пример: сравнение List и ListBuffer

// Immutable List
val list = List(1, 2, 3)
val updated = 0 :: list // создаёт новый список
// Mutable ListBuffer
val buf = scala.collection.mutable.ListBuffer(1, 2, 3)
buf += 4 // изменяет существующий объект

Смешивание

Scala позволяет использовать обе парадигмы — функциональную (immutable) и императивную (mutable) — в рамках одного проекта. Однако в крупных проектах и библиотеках по умолчанию используется immutable-подход, как более безопасный и чистый.

Идиомы и стилистика

  • Функции стандартной библиотеки (например, map, filter, fold, reduce) всегда возвращают новые коллекции, независимо от того, immutable они или mutable.

  • Mutable коллекции часто используются в алгоритмах, где важна производительность и много мутаций, но результат обычно преобразуется в immutable на выходе:

val buf = scala.collection.mutable.ListBuffer\[Int\]()
for (i <- 1 to 100) buf += i
val result: List\[Int\] = buf.toList

Это позволяет совмещать эффективность с безопасностью и читаемостью.