Работа с коллекциями #
Васильев Андрей Михайлович, 2024
Версии презентации
Коллекции в Kotlin #
Стандартная библиотека Kotlin предоставляет большой набор инструментов для работы с коллекциями — группами с переменным количеством элементов (или нулём элементов), которые используются для решения какой-либо задачи
Рассмотрим схему интерфейсов коллекций Kotlin
Типы коллекций #
Для трёх интересующих нас вариантов коллекций List, Set и Map доступны:
- Неизменяемые интерфейсы (List, Set и Map), которые предоставляют доступ только на чтение элементов
- Изменяемые интерфейсы (MutableList, MutableSet, MutableMap), которые предоставляют предыдущие интерфейсы, но также дополнительно дают возможности по изменению: добавлению, удалению и редактированию
Таким образом вы можете:
- Передавать в функции изменяемые коллекции в качестве аргументов, где принимается неизменяемая коллекция
- Возвращать изменяемую коллекцию в том случае, если функция должна вернуть неизменяемую коллекцию
Создание коллекций #
Самый распространённый способ создать коллекцию — использовать функции
listOf<T>()
, setOf<T>()
, mutableListOf<T>()
, mutableSetOf<T>()
Этим функциям в качестве аргументов можно передать элементы, разделяя их запятой:
val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()
Таким же образом можно создать ассоциативные списки — с помощью функций
mapOf<K,V>()
и mutableMapOf<K,V>()
Ключи и значения передаются как объекты Pair, создаваемых с помощью функции to
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
Компилятор достаточно умный, чтобы в большинстве случаев определить тип коллекции
Создание пустых коллекций #
Существуют функции для создания пустых коллекций: emptyList()
, emptySet()
и
emptyMap()
При создании пустой коллекции вы должны явно указывать тип элементов, которые будет содержать коллекция
val empty = emptyList<String>()
Обход элементов списков #
Интерфейс Iterable
предоставляет метод для получения итератора с помощью
которого можно пройтись по элементам коллекции
Также для коллекций доступно использование цикла for
:
val numbers = listOf("one", "two", "three", "four")
for (item in numbers) {
println(item)
}
Внутри цикла item
будет поочерёдно принимать значение из коллекции, тип данной
переменной автоматически выводится на основании типа коллекции
Операции коллекций #
Стандартная библиотека Kotlin предлагает большой набор функций для работы с коллекциями
- Простые операции: получение или добавление элементов
- Сложные операции: поиск, сортировка, фильтрация, преобразование и т. д.
Простые операции достаточно легко найти через среду разработки и документацию, рассмотрим применение сложных
Преобразование #
Данные функции создают новые коллекции из существующих на основе правил преобразования
Базовой функцией группы является map
, которая делегирует создание новых
элементом ассоциируемой ей лямбда-функции
val numbers = setOf(1, 2, 3)
numbers.map { it * 3 } // [3, 6, 9]
numbers.mapIndexed { idx, value -> value * idx } // [0, 2, 6]
- Лямбда-функция
map
получает в качестве аргумента значение элемента - Она возвращает новое значение, которое будет записано в новый массив
- Разработчику не надо заниматься созданием элементов
- Лямбда-функция
mapIndexed
получает 2 аргумента — номер элемента и его значение
Настоятельно рекомендуется ознакомиться с другими преобразованиями коллекций
Фильтрация коллекций #
В Kotlin условия фильтра задаются с помощью предикатов — лямбда-функций, которые принимают элемент коллекции, а возвращают логическое значение
true
означает, что элемент соответствует предикатуfalse
— не соответствует
Основная функция для фильтра коллекций - filter()
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 } // ["three", "four"]
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) ->
key.endsWith("1") && value > 10
} // {key11=11}
Настоятельно рекомендуется ознакомиться с другими функциями фильтрации
Поиск элемента по условию #
Функции first()
и last()
позволяют искать в коллекции элементы,
соответствующие заданному предикату
val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.first { it.length > 3 } // "three"
numbers.last { it.startsWith("f") } // "five"
Если ни один элемент не соответствует предикату, обе функции выбросят
исключение. Чтобы этого избежать, используйте firstOrNull()
и lastOrNull()
val numbers = listOf("one", "two", "three", "four", "five", "six")
numbers.firstOrNull { it.length > 6 } // null
Настоятельно рекомендуется ознакомиться с другими функциями получения элементов
Создание цепочек по преобразованию #
Использование функций преобразования позволяет оформить действие над коллекциями как последовательность нескольких шагов:
val smallNumberSquares =
listOf(5, 7, 13) // Оригинальный массив
.map { it * it } // Массив квадратов
.filter { it.toString().length < 3 } // Небольшие по длине числа
- Каждое конкретное действие достаточно простое и понятное
- Промежуточные результаты, не сохранённые в переменные, будут собраны сборщиком мусора
- Классической схемой при работе с данным подходом является:
- расширение обрабатываемых данных, расширяющее преобразование
- фильтрация данных на основании расширенной информации
- преобразование к финальному виду, сужающее преобразование
Изменяемые и неизменяемые переменные #
В Kotlin можно определить переменные с помощью ключевых слов val
и var
- Внутри переменных содержатся ссылки на объекты
- Ссылка внутри val-переменной не изменяется во время работы приложения
- В тоже самое время ссылка внутри var-переменной может измениться в любой момент времени исполнения приложения
Легче понимать что происходит в приложении, в котором минимум изменяемых элементов
Изменяемые объекты #
Ситуация становится сложнее, если объект, на который ссылается переменная сам может изменять своё внутреннее состояние, например — изменяемый список
Возможны четыре следующих состояния:
- Неизменяемая переменная, val, ссылающаяся на неизменяемый объект, например строку
- Неизменяемая переменная, val, ссылающаяся на изменяемый объект, например изменяемый список
- Изменяемая переменная, var, ссылающаяся на неизменяемый объект, например число
- Изменяемая переменная, var, ссылающаяся на изменяемый объект, например изменяемый набор
Очевидно, что понимать состояние первой переменной гораздо проще по сравнению со следующими вариантами
Также очевидно, что добавлять 2 степени свободы для одной переменной тоже редко когда необходимо