Коллекции
По определению коллекции – это группы с переменным количеством элементов (или нулем элементов). Объекты внутри как правило имеют единый тип. Коллекции называются коллекциями, потому что корнем их иерархии в языке является класс Collection<T>. Позже, при изучении наследования это станет понятнее, пока не будем сильно углубляться.
Типы коллекций
- List (список) – упорядоченный набор элементов, к ним можно обращаться по индексам. В списке могут встречаться дубликаты.
- Set (множество) – коллекция уникальных элементов. То есть повторяющиеся элементы исключены. Порядок элементов может быть любым.
- Map (словарь или ассоциативным список) – определенный набор пар. Пара содержит в себе ключ и значение. Зная ключ, можно обратиться к какому-нибудь значению. Ключи строго уникальны, а значения могут иметь дубликаты.
Списки
В этот раз подробнее остановимся на списках. Поведение напоминает работу с массивами, однако, есть существенные отличия в принципе работы.
Отличия от массивов
- Первое. Массив имеет строго фиксированный размер и не может уменьшаться или увеличиваться. Изменить размер массива можно только создав его копию с дополнительными или утраченными элементами. Списки же имеют методы add и remove для добавления/удаления элементов.
- Второе. Массив предоставляется классом Array<T>. List<T> является интерфейсом и имеет разные реализации со своим функционалом.
- Третье. Массивы оптимизированы для примитивов и меют отдельные типа IntArray, CharArray и т.д. Списки такой оптимизации не имеют.
- Четвертое. Различается процесс сравнивания элементов друг с другом. В массивах сравниваются адреса ячеек в памяти, в списках же идет сравнение самого значения.
В целом хорошей практикой считается использовать списки вместо массивов везде, где это позволяет техническая реализация. Однако, с точки зрения производительности, массивы имеют над списками преимущество.
Создание списков в Котлин
Списки создаются с помощью функции listOf() из стандартной библиотеки Kotlin. Короткая запись создания целочисленного списка выглядит так:
val list = listOf(4, 4, 2)
Если принудительно проставить тип списка, получим List. Это базовый интерфейс для всех неизменяемых списков:
val list: List<Int> = listOf(4, 4, 2)
Его “неизменяемость” определяется тем, что нельзя просто так взять и обратиться по индексу к элементу и заменить его на другой. Как мы делали это с массивами. Также отсутствуют функции по добавлению и удалению элементов.
Изменяемые списки в Kotlin
В таких списках мы можем читать конкретное значение по индексу, а также пробегать циклом по всем значениям вместе. Но для того, чтобы создать изменяемый список, необходимо использовать функцию mutableListOf().
При данной инициализации тип переменной будет MutableList<T>
. Это второй базовый интерфейс для списков, на основе которого создаются изменяемые списки. Это List с возможностью добавления или удаления элементов:
val list3: MutableList<Int> = mutableListOf(1, 2, 3)
Функции для работы со списками
Теперь переходим к инструментарию по работе со списками. В уроке примеры будут представлены на примере целочисленного списка. А в практике, прилагаемой к уроку, будут оформлены задачи с применением различных типов с неким прикладным контекстом.
Еще раз создадим изменяемый список с некоторым количеством чисел.
val mutableList = mutableListOf(11, 15, 20, 12, 9, 14)
Функция add()
Сначала будем добавлять значения в список с помощью функции add(). Чтобы воспользоваться ей, необходимо написать переменную со списком и вызвать для нее этот метод с помощью обращения через точку.
Метод add() может принимать два набора параметров и в зависимости от этого, будет произведены разные действия.
Первый вариант – это всего один параметр, который является новым элементом, который нужно добавить в список. Этот элемент по умолчанию добавляется в конец списка. Результат в консоли:
val mutableList = mutableListOf(11, 15, 20, 12, 9, 14)
println(mutableList)
mutableList.add(42)
println(mutableList)
Второй вариант – это когда метод add() принимает два параметра. На первом месте прописывается индекс – то место, куда должен быть вставлен элемент. Проставим ноль. На втором месте, через запятую, сам элемент. Функция, принимающая два параметра, выглядит так. Результат – число 42 добавлено в начало списка.
mutableList.add(0, 42)
println(mutableList)
Кстати, во время занесения параметров в функцию можно вывести подсказку о том, какой именно параметр сейчас нужно ввести. Это окошко не всегда появляется, но оно вызовется с помощью сочетаний клавиш ctrl + P или cmd + P на маке. Курсор при этом должен находиться внутри скобок, где вводятся параметры метода.
Функция contains()
Следующая функция проверит наличие элемента в списке и вернет Boolean значение true или false. По тому же принципу, как делали это с помощью in в уроке про диапазоны. Сейчас применим метод contains() к списку со значением 42 и получим true. Число 42 содержится в списке mutableList.
println(mutableList.contains(42))
Методы isEmpty() и isNotEmpty()
Следующие функции часто пригождаются на практике при создании условий, когда нужно проверять: содержит ли список хоть какой элемент или же он пуст. Это методы isEmpty() и isNotEmpty(). Соответственно методы возвращают значения true или false.
println(mutableList.isEmpty())
println(mutableList.isNotEmpty())
Следующие функции помогут узнать индекс какого-либо элемента, если таковой присутствует в списке. Вызываем метод indexOf() с параметром 42 и получаем индекс 0. Все так. Если одинаковых значений в списке несколько, будет возвращено будет первое, которое встретится. Чтобы узнать последний индекс встречающегося элемента воспользуемся функцией lastIndexOf():
println(mutableList.indexOf(42))
println(mutableList.lastIndexOf(42))
Функции sort() и reverse()
Чтобы отсортировать список есть удобные функции. sort() отсортирует по возрастанию и sortDescending() по убыванию соответственно. Кроме того можно воспользоваться функцией reverse() для изменения порядка элементов в списке на обратный. После применения этих трех методов элементы будут расположены по возрастанию.
mutableList.forEach {
println(it)
}
Мы еще не изучали лямбды, но еще немного задержимся тут с точки зрения возможностей оформления. Автоматически задекларированную переменную it можно:
- во-первых: явно прописать, вызвав контекстное меню и выбрав принудительную простановку сигнатуры лямбды,
- во-вторых: задать произвольное имя переменной, которая будет использоваться внутри тела лямбды. Часто этот прием нужен для повышения читабельности кода. Для этого it меняем на произвольное название и теперь эту переменную можно использовать внутри.
Ок. Очень похоже на принцип работы цикла for, не правда ли? Способ с forEach() выглядит более аккуратным и без дополнительных переменных из-за его стилистики функционального программирования. Но есть нюансы, связанные с удобностью и производительностью для списков или интервалов. Просто перечислю результаты наблюдений из которых можно выбрать собственный способ исходя из ситуации:
- Если используется интервал, следует использовать цикл for.
- Если используется коллекция, типа списка, то следует использовать forEach.
- Если есть необходимость использовать операторы continue или break, то удобнее всего делать это в цикле for.
Расскажу еще про пару популярных функций, обязательных к изучению.
Функция filter()
Основная функция для фильтрации коллекций – filter(). Условие задается фигурных скобках, где указывается логическое выражение, которое возвращает true или false. Так как функция не изменяет список, ее можно использовать и с изменяемыми списками.
Напишем этот пример. Отфильтрованный список будет помещен в новую переменную. В фигурных скобках пишем условие. it (элемент коллекции) равен, например, 42. Ниже сделаем вывод нового списка через forEach(). Таким образом каждый элемент будет проверяться на равенство с числом 42 и если оно верно, элемент запишется в новый список.
val mutableList2 = mutableList.filter{
it == 42
}
mutableList2.forEach{
println(it)
}
Функция map()
И еще одна полезная функция для коллекций – map(). Используется, если нам нужно взаимодействовать со всеми элементами. Конечно, все это можно сделать в цикле for, но так изящнее и соответствует стилю функционального программирования. Применив функцию map(), можно обойти все элементы списка и записать их преобразования в новый список. Для демонстрации подойдет любая простая операция. Умножим на 2 все элементы из списка mutableList2 – там сейчас содержится два числа 42. Сохраним результат преобразования в новый список mutableList3 и выведем его в консоль:
val mutableList3 = mutableList2.map{
it * 2
}
println(mutableList3)
Далее на практике написания приложений мы часто будем сталкиваться с функциями-расширениями и даже научимся создавать свои. Сейчас это все, что я хотел сказать по стандартным функциям для коллекций.
Для тех, кто собрался стать Android-разработчиком
Пошаговая
схема
Описание процесса обучения от основ Kotlin до Android-разработчика
Бесплатные
уроки
Авторский бесплатный курс по основам языка программирования Kotlin
Обучающий
бот
Тренажер и самоучитель по Котлин – бесплатные тесты и практика