Списки, множества и словари
Коллекции Scala по умолчанию неизменяемы — и это меняет то, как вы думаете о данных.
«Неизменяемая коллекция — это снимок данных: его можно безопасно передавать куда угодно, не боясь, что кто-то его испортит.»
Три рабочие лошадки коллекций: List (упорядоченная последовательность), Set (уникальные элементы) и Map (пары ключ-значение). По умолчанию все они неизменяемые: любая операция возвращает новую коллекцию, не трогая исходную.
val nums = List(1, 2, 3, 4)
val unique = Set(1, 2, 2, 3) // Set(1, 2, 3) — дубликаты ушли
val ages = Map("Аня" -> 25, "Иван" -> 30)
println(nums.head) // 1 — первый элемент
println(nums(2)) // 3 — по индексу
println(ages("Аня")) // 25 — по ключуСинтаксис "Аня" -> 25 — это пара (ключ, значение). Стрелка -> создаёт кортеж из двух элементов.
«Изменение» создаёт новую коллекцию
val nums = List(1, 2, 3)
val more = nums :+ 4 // добавить в конец -> List(1,2,3,4)
val pre = 0 :: nums // добавить в начало -> List(0,1,2,3)
println(nums) // List(1,2,3) — оригинал цел!
println(more) // List(1,2,3,4)Оператор :: («cons») добавляет элемент в начало списка — это очень дешёвая операция для List.
Проверки и размер
val s = Set(1, 2, 3)
println(s.contains(2)) // true
println(s.size) // 3
println(List().isEmpty) // trueТа же идея на Python ▶
# В Python list изменяем, но идею снимка передаёт tuple/frozenset
nums = [1, 2, 3, 4]
unique = set([1, 2, 2, 3]) # {1, 2, 3}
ages = {"Аня": 25, "Иван": 30}
print(nums[0], nums[2]) # 1 3
print(ages["Аня"]) # 25
# Неизменяемое "добавление" — новый список
more = nums + [5] # новый список
print(nums, more) # оригинал цел
print(2 in unique, len(unique)) # True 3List(1,2,3) :+ 4 -> List(1,2,3,4) (новый)
|
оригинал НЕ меняется -> всё ещё List(1,2,3)Как работает под капотом (JVM)
Неизменяемый List в Scala — это односвязный список: каждый элемент хранит значение и ссылку на хвост. Поэтому добавление в начало (::) мгновенно — создаётся одна новая «голова», а хвост переиспользуется (это называется структурное разделение, structural sharing). Старая коллекция остаётся валидной, потому что её узлы не меняются. Map и Set реализованы как сбалансированные деревья или хеш-структуры, тоже с разделением. Благодаря неизменяемости коллекции безопасны для многопоточности без блокировок.
Частые ошибки
- Ждать, что операция изменит коллекцию. Она возвращает новую; присвойте результат.
- Добавлять в конец
Listв цикле. Это медленно (O(n)); добавляйте в начало или используйтеVector. - Обращаться к
list(i)по индексу часто. УListэто медленно; для индексного доступа беритеVectorили массив.
Best practices
- По умолчанию используйте неизменяемые коллекции — они безопаснее.
- Для частого индексного доступа выбирайте
Vector, для начала-добавления —List. - Помните, что результат операций нужно присвоить — оригинал не меняется.
Почему неизменяемость по умолчанию — правильный выбор
Решение сделать коллекции неизменяемыми по умолчанию определяет весь стиль работы с данными в Scala. Неизменяемую коллекцию можно без опаски передать в любую функцию: она не сможет её испортить, в худшем случае вернёт новую. Можно хранить её как ключ или делиться ею между потоками без блокировок. Целый класс коварных багов — кто-то неожиданно изменил список, который вы держали, — просто исчезает.
Вопрос производительности часто пугает новичков: «разве создавать новую коллекцию на каждое изменение не дорого?» Ответ — структурное разделение. Неизменяемые структуры переиспользуют общие части между версиями, поэтому «новая» коллекция обычно делит большую часть данных со старой. Добавление в начало списка не копирует список, а создаёт одну новую ссылку. Понимание этого механизма снимает страх и помогает выбирать правильную коллекцию под задачу.
Выбор конкретной коллекции под задачу — навык, который приходит с опытом, но базовое правило простое: List для последовательной обработки и добавления в начало, Vector для частого доступа по индексу, Set для уникальности, Map для связи ключей со значениями. Начав с этих ориентиров, вы редко ошибётесь, а тонкую оптимизацию оставите на потом.
Итоги. List, Set, Map — неизменяемые по умолчанию; операции создают новые коллекции с переиспользованием структуры. Дальше — преобразование коллекций через map и filter.