Перейти к содержанию

Collections

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

Основные коллекции

List, Set и Map

  • List<T> сохраняет порядок элементов, поддерживает доступ по индексу и допускает дубликаты.
  • Set<T> хранит уникальные элементы. Не полагайтесь на порядок обхода, если он не определён конкретной реализацией.
  • Map<K, V> хранит пары ключ-значение с уникальными ключами. Это вид коллекции Kotlin, но он не наследует Collection.

Выбор зависит от смысла данных. Например, список подходит для упорядоченных элементов UI, множество - для выбранных идентификаторов, а map - для поиска объекта по идентификатору.

List vs MutableList

List<T> - интерфейс только для чтения: он не предоставляет add(), remove() или присваивание по индексу. MutableList<T> расширяет его операциями изменения.

val mutable = mutableListOf(1, 2)
val readOnly: List<Int> = mutable

mutable.add(3)
println(readOnly) // [1, 2, 3]

Доступ только для чтения не означает неизменяемость. Обе ссылки указывают на один объект, поэтому изменения через другую ссылку остаются видимыми. Аналогично, val запрещает переприсвоить переменную, но не изменять саму коллекцию:

val items = mutableListOf("A")
items += "B" // допустимо

Возвращайте List<T>, если вызывающий код не должен менять коллекцию. Если ему также нужен стабильный снимок, создайте защитную копию, например source.toList(), и не сохраняйте или не публикуйте изменяемый псевдоним. Копия остаётся поверхностной: изменяемые элементы внутри неё могут меняться. Для более строгих гарантий существуют персистентные неизменяемые коллекции из отдельной библиотеки.

Операции

Преобразование, отбор и агрегация

Основные операции хорошо выражают намерение:

  • map преобразует каждый элемент.
  • filter оставляет подходящие элементы.
  • flatMap преобразует элементы в итерируемые значения и объединяет их.
  • fold сворачивает элементы, начиная с заданного аккумулятора.
  • forEach выполняет побочные эффекты; для создания значений используйте преобразования.
val names = users
    .filter { it.isActive }
    .map { it.name }

val totalAge = users.fold(0) { total, user -> total + user.age }

Эти операции возвращают результат, не меняя исходную коллекцию. Такие функции, как sort(), сортируют изменяемый список на месте, а sorted() возвращает новый список. В коде управления состоянием это различие должно быть явным.

Цепочки операций над коллекциями выполняются немедленно и могут создавать промежуточные коллекции. asSequence() обрабатывает цепочку лениво и иногда позволяет избежать промежуточных результатов или остановиться раньше, например перед first() или после take(). Но sequence добавляет накладные расходы, поэтому для маленьких коллекций и простых цепочек он не обязательно быстрее. Критичные участки следует измерять.

Практические рекомендации

  • Храните изменяемые коллекции внутри их владельца, а наружу передавайте интерфейсы для чтения или снимки.
  • Не меняйте коллекцию во время обхода, если API явно не поддерживает такую операцию.
  • Не используйте изменяемые объекты как элементы set или ключи map, если могут измениться поля, участвующие в equals() или hashCode().
  • Предпочитайте операции, ясно выражающие намерение, но используйте цикл, если он понятнее или позволяет избежать лишних выделений памяти в критичном коде.

Связанные темы

Источники