Коллекции: 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 покрывают почти все потребности в хранении данных, а разделение на изменяемые и неизменяемые версии помогает писать предсказуемый код. В следующем уроке мы научимся преобразовывать эти коллекции декларативно.