Работа с коллекциями в Kotlin

Работа с коллекциями #

Васильев Андрей Михайлович, 2024

Версии презентации


Коллекции в Kotlin #

Стандартная библиотека Kotlin предоставляет большой набор инструментов для работы с коллекциями — группами с переменным количеством элементов (или нулём элементов), которые используются для решения какой-либо задачи

Рассмотрим схему интерфейсов коллекций Kotlin

diagram


Типы коллекций #

Для трёх интересующих нас вариантов коллекций 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-переменной может измениться в любой момент времени исполнения приложения

diagram

Легче понимать что происходит в приложении, в котором минимум изменяемых элементов


Изменяемые объекты #

Ситуация становится сложнее, если объект, на который ссылается переменная сам может изменять своё внутреннее состояние, например — изменяемый список

Возможны четыре следующих состояния:

  • Неизменяемая переменная, val, ссылающаяся на неизменяемый объект, например строку
  • Неизменяемая переменная, val, ссылающаяся на изменяемый объект, например изменяемый список
  • Изменяемая переменная, var, ссылающаяся на неизменяемый объект, например число
  • Изменяемая переменная, var, ссылающаяся на изменяемый объект, например изменяемый набор

Очевидно, что понимать состояние первой переменной гораздо проще по сравнению со следующими вариантами

Также очевидно, что добавлять 2 степени свободы для одной переменной тоже редко когда необходимо

© A. M. Васильев, 2024, CC BY-SA 4.0, andrey@crafted.su