Операции коллекций: общий обзор

Стандартная библиотека Kotlin предлагает большой набор функций для работы с коллекциями. Сюда входят простые операции, такие как получение или добавление элементов, а также более сложные, например, поиск, сортировка, фильтрация, преобразование и т. д.

Функции-расширения (Extension) и функции-элементы (member functions)

Операции коллекций в стандартной библиотеке объявляются двумя способами: как функции-элементы и как функции-расширения.

С помощью функций-элементов определяются операции, которые необходимы для определённых типов коллекций. Например, у Collection есть функция isEmpty() для проверки того, что коллекция пуста; у List есть функция get() для обращения к элементу по его индексу и т. д.

Если вы решите создать свою собственную реализацию коллекции, например, на основе интерфейса Collection, то вы также должны будете реализовать все его функции-элементы. Чтобы упростить эту задачу используйте специально подготовленные реализации интерфейсов из стандартной библиотеки: AbstractCollection, AbstractList, AbstractSet, AbstractMap. Помимо вышеперечисленных есть аналоги для создания изменяемых (mutable) коллекций.

Другие операции коллекций объявлены как функции-расширения. К ним относятся операции для фильтрации, преобразования, сортировки и прочих видов обработки элементов коллекции.

Общие операции

Общие операции доступны как для изменяемых, так и для неизменяемых коллекций. Их можно разделить на следующие группы:

Операции, описанные в разделах выше, возвращают результат, не изменяя исходную коллекцию. Например, операция filter создаёт новую коллекцию, которая содержит элементы, соответствующие условию фильтра. Результаты этих операций можно использовать несколькими способами, например, сохранять в переменные или передавать в другие функции.

fun main() {
    val numbers = listOf("one", "two", "three", "four")  
    numbers.filter { it.length > 3 }  // список `numbers` не изменится, результат фильтра теряется
    println("numbers are still $numbers") // [one, two, three, four]
    val longerThan3 = numbers.filter { it.length > 3 } // результат сохраняется в `longerThan3`
    println("numbers longer than 3 chars are $longerThan3") // [three, four]
}

Некоторые функции в качестве параметра принимают целевой объект - destination. Он представляет из себя изменяемую коллекцию, в которую функция добавляет результаты своих вычислений, вместо того, чтобы самостоятельно создавать новый объект. Названия таких функций можно отличить по постфиксу To, например, filterTo() вместо filter() или associateTo() вместо associate().

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    val filterResults = mutableListOf<String>()  // целевой объект
    numbers.filterTo(filterResults) { it.length > 3 }
    numbers.filterIndexedTo(filterResults) { index, _ -> index == 0 }
    println(filterResults) // содержит результат обеих операций - [three, four, one]
}

Эти функции возвращают целевой объект обратно, поэтому его можно создать прямо при вызове функции:

fun main() {
    val numbers = listOf("one", "two", "three", "four")
    // отфильтрованные элементы будут помещены в `HashSet`,
    // который автоматом исключит дубликаты
    val result = numbers.mapTo(HashSet()) { it.length }
    println("distinct item lengths are $result") // [3, 4, 5]
}

Почти все стандартные функции имеют аналог в виде функции, работающей с целевым объектом. С полным списком можно ознакомиться в документации.

Операции записи

Для изменяемых коллекций доступны так называемые операции записи, которые изменяют состояние коллекции. Это операции по добавлению, удалению и обновлению элементов. Подробнее они рассматриваются в отдельном разделе - Операции записи, а также частично в разделах List: специфичные операции и Map: специфичные операции.

Помимо прочего существуют пары функций, которые выполняют одинаковые операции, но с одной лишь разницей: одна функция изменяет исходную коллекцию и поэтому работает только с изменяемыми типами, тогда как вторая возвращает результат в виде новой коллекции и таким образом позволяет работать с изменяемыми и неизменяемыми типами коллекций. Например, функция sort() сортирует исходную изменяемую коллекцию, тем самым меняя её состояние; функция sorted() создаёт новую коллекцию, содержащую те же элементы, что и исходная, но в отсортированном порядке.

fun main() {
    val numbers = mutableListOf("one", "two", "three", "four")
    val sortedNumbers = numbers.sorted()
    println(numbers == sortedNumbers)  // false
    numbers.sort()
    println(numbers == sortedNumbers)  // true
}