Коллекции: List, Set, Map

Kotlin различает изменяемые и неизменяемые коллекции на уровне типов: List, Set и Map по умолчанию только для чтения, а их mutable-версии позволяют менять содержимое.
Суть: правильный выбор между неизменяемой и изменяемой коллекцией делает код безопаснее, а богатый набор операций над коллекциями избавляет от ручных циклов.

Три базовые структуры данных встречаются в любом приложении. List — упорядоченная последовательность с доступом по индексу и возможными дубликатами. Set — множество уникальных элементов без гарантии порядка. Map — словарь пар «ключ — значение». Создаются они функциями listOf, setOf, mapOf.

val nums = listOf(1, 2, 2, 3)         // List, дубликаты разрешены
val unique = setOf(1, 2, 2, 3)        // Set -> {1, 2, 3}
val ages = mapOf("Аня" to 19, "Иван" to 21)

println(nums[0])          // 1, доступ по индексу
println(unique.size)      // 3, дубликат убран
println(ages["Аня"])      // 19, доступ по ключу

Изменяемые и неизменяемые

Функции listOf/setOf/mapOf создают коллекции только для чтения: добавить или удалить элемент нельзя. Для изменяемых нужны mutableListOf, mutableSetOf, mutableMapOf. По умолчанию выбирайте неизменяемые — так никто случайно не испортит данные, а в многопоточной среде это ещё и безопаснее.

val readOnly = listOf("a", "b")
// readOnly.add("c")    // ОШИБКА: нет метода add

val editable = mutableListOf("a", "b")
editable.add("c")        // ок
editable.removeAt(0)     // ок
println(editable)        // [b, c]

Как работает под капотом

На уровне реализации listOf часто возвращает тот же тип, что и mutableListOf, но через интерфейс List, в котором нет методов изменения. То есть неизменяемость здесь — это контракт интерфейса, а не отдельная структура данных. Поэтому неизменяемая ссылка не означает абсолютной защиты от изменений через другую ссылку — но для повседневной разработки этот контракт даёт нужную дисциплину и подсказки компилятора.

  Иерархия коллекций (упрощённо)

  Collection
    |-- List      listOf()        (только чтение)
    |     '-- MutableList  mutableListOf()
    |-- Set       setOf()
    |     '-- MutableSet   mutableSetOf()
  Map (отдельно)  mapOf() / mutableMapOf()

Частые ошибки

Везде использовать mutable. Изменяемые коллекции нужны реже, чем кажется. По умолчанию берите неизменяемые: меньше неожиданных мутаций.

Обращаться по несуществующему ключу через []. map["нет"] вернёт null, а не упадёт; учитывайте это и обрабатывайте null.

Менять коллекцию во время перебора. Это приводит к ошибке конкурентной модификации; собирайте изменения отдельно или используйте подходящие операции.

Best practices

  • По умолчанию неизменяемые коллекции; mutable — только когда действительно меняете содержимое.
  • Для доступа к Map учитывайте возможный null или используйте getOrDefault/getOrElse.
  • Возвращайте из функций тип List, а не MutableList, чтобы не раскрывать изменяемость наружу.

Поведение Set (удаление дубликатов) и Map (доступ по ключу) удобно проверить на Python. Запустите врезку.

# Аналоги List/Set/Map из Kotlin
nums = [1, 2, 2, 3]
unique = set(nums)
ages = {'Аня': 19, 'Иван': 21}

print('list:', nums, 'len', len(nums))
print('set :', sorted(unique), 'len', len(unique))
print('map :', ages.get('Аня'), ages.get('нет', 'не найдено'))

Попробуй сам ▶ — добавьте дубликаты в список и сравните длину list и set. В Kotlin set точно так же оставит только уникальные значения.

Закрепим главное

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

Второй практический ориентир — выбор структуры под задачу. Нужен порядок и доступ по индексу — берите List. Важна только уникальность и быстрая проверка наличия — Set. Нужна связь «ключ — значение» — Map. Неправильно выбранная структура заставляет писать лишний код: например, ручную проверку дубликатов там, где Set сделал бы это сам. Правильный выбор коллекции — это половина чистого решения задачи.

Итог: List, Set и Map покрывают почти все потребности в хранении данных, а разделение на изменяемые и неизменяемые версии помогает писать предсказуемый код. В следующем уроке мы научимся преобразовывать эти коллекции декларативно.

Проверьте себя
1. Что произойдёт, если вызвать add() у коллекции, созданной через listOf()?
AЭлемент добавится в конец списка
BБудет ошибка компиляции: у List нет метода add
CСписок молча проигнорирует вызов
DСоздастся новая копия списка
2. Чем Set отличается от List?
ASet хранит пары ключ-значение
BSet гарантирует порядок, а List нет
CSet хранит только уникальные элементы, дубликаты отбрасываются
DМежду ними нет разницы