Перейти к основному содержимому
Мобилка06ГЛАВА 06Коллекции: список, словарь, множествоList — по номеруMap — по ключуSet

Коллекции: список, словарь, множество

Прочитано0%
Квизы0/4
Тренажёры0/3
Уровень 1 · Новичок

Зачем это нужно​

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

Заводить под каждого отдельную переменную бессмысленно по той же причине, по которой мы отказались от name1, score1: их количество заранее неизвестно, и перебрать их циклом нельзя.

Для этого есть три вида хранилищ, и различаются они тем, как из них достают данные:

  • List — по порядковому номеру: первый, второй, третий. Порядок есть, повторы разрешены.
  • Map — по ключу: «сколько очков у Алисы». Порядок неважен, ключи уникальны.
  • Set — никак: из него нельзя достать «третий элемент», зато он сам следит, чтобы значения не повторялись.

В этой главе разберём все три, научимся обходить их циклом и преобразовывать цепочкой filter и map — той самой, которой в главе про функции мы отбирали значения.

Списки: List и MutableList​

Теперь, когда есть тип для одного участника, соберём их в команду. List — упорядоченная коллекция элементов одного типа: у каждого элемента есть индекс (номер позиции), начиная с 0.

KOTLIN · КОЛЛЕКЦИИ — ТРИ ПОЛКИList[0]Алиса[1]Богда[2]Соняпорядок и индекс важныMapАлиса→82Богдан→91Соня→77доступ по ключу, не по номеруSet82917782повтор 82 внутрь не попал — набор без дублей
Три полки для данных: List хранит по порядку и номеру, Map — по ключу, Set — только уникальные значения без порядка.
Нажми на часть выражения
fun main() { val names = listOf("Алиса", "Богдан", "Соня") for (name in names) { println(name) } println(names[1]) println(names.size) }

Разбор по строкам​

  • Строка 2: listOf("Алиса", "Богдан", "Соня") — функция-фабрика (как mapOf/setOf дальше в главе): создаёт List<String> — список строк. Список, созданный через listOf, — read-only: в него нельзя ни добавить, ни убрать элемент (попробуем и увидим ошибку компиляции в разделе «Типичные ошибки»).
  • Строки 3–5: for (name in names) { println(name) } — цикл проходит по всем трём элементам по очереди: сначала name = "Алиса", затем name = "Богдан", затем name = "Соня", печатая каждый.
  • Строка 6: names[1] — новый символ, квадратные скобки [ ] — оператор индексации (index operator): достаёт элемент списка по номеру позиции. Отсчёт всегда идёт с нуля: names[0] — «Алиса», names[1] — «Богдан», names[2] — «Соня». names[1] — это не то же самое, что «второй по счёту» в обычной речи, а «элемент под индексом 1».
  • Строка 7: names.size — свойство size (без скобок — это не метод, а свойство, готовое значение) хранит количество элементов.

Что выведет:

Алиса
Богдан
Соня
Богдан
3

Первые три строки — цикл, четвёртая — names[1] («Богдан» под индексом 1), пятая — names.size (в списке три элемента).

Списки можно строить и из своих классов — например, из Competitor, а не только из строк и чисел. Тогда к каждому элементу применимы уже знакомые из главы 1 map/filter/sum.

fun main() { val team = listOf( Competitor("Алиса", 82), Competitor("Богдан", 91), Competitor("Соня", 77) ) val totalScore = team.map { it.score }.sum() println(totalScore) } data class Competitor(val name: String, var score: Int)

Разбор по строкам​

  • Строки 2–6: val team = listOf(...) — список из трёх объектов Competitor, каждый со своим именем и очками. Список можно писать в несколько строк — запятая после каждого элемента, кроме последнего, работает так же, как и в одну строку.
  • Строка 7: team.map { it.score }.sum() — та же цепочка map/sum из главы 1, только теперь it — не число, а целый объект Competitor. it.score достаёт из каждого объекта одно свойство, map собирает из этих свойств новый список чисел, sum() складывает их.

Что выведет: 250 — потому что 82 + 91 + 77 = 250.

Та же команда, но теперь цепочка filter/map из главы 1 — предскажи, какой список напечатается:

fun main() {
  val team = listOf(
      Competitor("Алиса", 82),
      Competitor("Богдан", 91),
      Competitor("Соня", 77)
  )
  val qualified = team.filter { it.score >= 80 }.map { it.name }
  println(qualified)
}

data class Competitor(val name: String, var score: Int)

Что напечатает этот код? Сначала ответь, потом проверяй.

Та же цепочка — теперь в замедленной съёмке: прогони каждый элемент через конвейер filter/map по шагам и посмотри, где элемент отбрасывается, а где превращается.

team.filter { it.score >= 80 }.map { it.name }
team
Алиса · 82Богдан · 91Соня · 77
filter { it.score >= 80 }
после filter
пока пусто
map { it.name }
qualified
пока пусто
Та же цепочка, что в примере выше: filter отбирает участников с score >= 80, map оставляет только имена. Жми «Шаг» — элементы пойдут по конвейеру по одному.
Шаг 0 из 5

Список из listOf — неизменяемый (в том смысле, что через сам этот List нельзя ни добавить, ни удалить элемент). Когда список должен расти и меняться, используют mutableListOf — тот же список, но с методами add/remove.

fun main() { val roster = mutableListOf("Алиса", "Богдан") roster.add("Соня") roster.remove("Алиса") println(roster) }

Разбор по строкам​

  • Строка 2: mutableListOf("Алиса", "Богдан") — создаёт MutableList<String>: список с теми же методами, что у обычного List (size, [индекс], map, filter...), плюс методы изменения.
  • Строка 3: roster.add("Соня") — добавляет элемент в конец списка.
  • Строка 4: roster.remove("Алиса") — убирает из списка первое вхождение значения "Алиса" (не по индексу, а по значению).
  • Строка 5: println(roster) — списки печатаются в квадратных скобках через запятую — то же текстовое представление, что мы уже видели в главе 1 у результата map.

Что выведет: [Богдан, Соня] — «Соня» добавилась в конец, «Алиса» удалилась, порядок остальных элементов сохранился.

Отвечено вопросов: 0 из 4
Что означает for (name in names)?
Чем names[1] отличается от «второго элемента» в обычной речи?
Чем listOf(...) отличается от mutableListOf(...)?
Что выведет team.map { it.score }.sum(), если team — список из трёх объектов Competitor?
Отвечено верно: 0 из 4

Map: пары ключ — значение​

Map хранит не просто значения, а пары «ключ → значение» (key-value pair): вместо доступа по номеру позиции, как у List, доступ идёт по ключу — например, по имени участника.

Нажми на часть выражения
fun main() { val scores = mapOf("Алиса" to 82, "Богдан" to 91, "Соня" to 77) println(scores["Богдан"]) for ((name, score) in scores) { println("$name: $score") } }

Разбор по строкам​

  • Строка 2 — создание карты: три пары «имя → очки».
  • Строка 3: scores["Богдан"] — тот же оператор индексации [ ], что и у списков, только теперь в скобках не номер позиции, а ключ. Возвращает значение, связанное с этим ключом.
  • Строка 4: for ((name, score) in scores) — перебор карты: на каждой итерации for даёт не один элемент, а пару, которую сразу деструктурируют (та же деструктуризация, что мы видели у data class) на name (ключ) и score (значение).
  • Строка 5: println("$name: $score") — строковый шаблон из главы 1, теперь с двумя подстановками подряд.

Что выведет:

91
Алиса: 82
Богдан: 91
Соня: 77

Первая строка — прямой доступ по ключу "Богдан". Дальше — цикл: mapOf сохраняет порядок, в котором пары были перечислены при создании, поэтому вывод идёт именно в порядке Алиса → Богдан → Соня, а не в каком-то случайном.

Как и у List, есть изменяемая версия — MutableMap, с методами добавления и обновления записей.

fun main() { val scores = mutableMapOf("Алиса" to 82, "Богдан" to 91) scores["Соня"] = 77 scores["Алиса"] = 85 println(scores) println(scores.getOrDefault("Дима", 0)) }

Разбор по строкам​

  • Строка 3: scores["Соня"] = 77 — новая роль тех же квадратных скобок: индексация слева от = не читает значение, а кладёт его в карту. Если ключа "Соня" ещё не было — добавляет новую пару; если был бы — заменил бы значение.
  • Строка 4: scores["Алиса"] = 85 — ключ "Алиса" уже существовал (со значением 82), поэтому это не добавление, а замена: новое значение 85 затирает старое 82.
  • Строка 6: scores.getOrDefault("Дима", 0) — безопасный способ спросить значение по ключу, которого может не быть. Если бы мы написали просто scores["Дима"], то получили бы null («ничего») — про null в этом курсе будет отдельная глава, а пока getOrDefault обходит эту проблему: если ключа нет, возвращает второй аргумент как запасное значение.

Что выведет:

{Алиса=85, Богдан=91, Соня=77}
0

Карта печатается в фигурных скобках, пары через запятую в формате ключ=значение. "Алиса" теперь 85, а не 82 — значение заменилось. Для несуществующего ключа "Дима" вернулся запасной 0.

Set: только уникальные значения​

Set — коллекция без повторов: если добавить значение, которое уже есть, ничего не произойдёт — набор просто не станет длиннее.

fun main() { val uniqueScores = setOf(82, 91, 82, 77, 91) println(uniqueScores) println(uniqueScores.size) }

Разбор по строкам​

  • Строка 2: setOf(82, 91, 82, 77, 91) — передали пять чисел, но 82 и 91 встречаются дважды. Set хранит только уникальные значения, поэтому повторы схлопываются в один экземпляр каждое.
  • Строка 3–4 — печать: Set, как и List, выводится в квадратных скобках, порядок — тот, в котором значения встретились впервые.

Что выведет:

[82, 91, 77]
3

Из пяти переданных чисел осталось три уникальных: 82 и 91 каждое учтено один раз, несмотря на то что были указаны дважды. Как и у списков и карт, есть mutableSetOf с методами add/remove — работает по тем же правилам, что и mutableListOf.

Важно

Именно здесь особенно важна связка equals()/hashCode() из раздела про data class: Set и ключи Map определяют уникальность через них. У data class она согласована автоматически — два структурно одинаковых объекта Competitor в Set схлопнутся в один, как схлопнулись одинаковые числа выше. У обычного класса это не сработает: два объекта с одинаковыми полями будут считаться разными, потому что сравниваются по ссылке.

Отвечено вопросов: 0 из 4
Что делает "Алиса" to 82 в mapOf("Алиса" to 82)?
Что делает scores["Соня"] = 77 для MutableMap?
Что выведет setOf(82, 91, 82, 77, 91)?
Почему getOrDefault("Дима", 0) безопаснее, чем scores["Дима"], если ключа "Дима" нет?
Отвечено верно: 0 из 4

Типичные ошибки​

Сообщения ниже сняты вживую с той же песочницы Kotlin Playground, что работает в кнопках Run этой главы.

3. add() у List, а не у MutableList​

fun main() { val names: List<String> = listOf("Алиса", "Богдан") names.add("Соня") }
Unresolved reference 'add'.

Перевод: «Неразрешённая ссылка add». List — интерфейс только для чтения: у него нет метода add, он просто не существует у этого типа, поэтому компилятор его не находит — так же, как не нашёл бы опечатку в имени переменной.

Как починить: объявить список как изменяемый — val names: MutableList<String> = mutableListOf("Алиса", "Богдан"), — и тогда add появится.

5. Map возвращает значение, которого может не быть​

fun main() { val scores = mapOf("Алиса" to 82, "Богдан" to 91) val aliceScore: Int = scores["Алиса"] println(aliceScore) }
Initializer type mismatch: expected 'Int', actual 'Int?'.

Перевод: «Несовпадение типа начального значения: ожидался Int, а по факту Int?». Обращение к карте по ключу через [ ] всегда возвращает «может быть Int, а может быть ничего» (Int? — nullable-тип, потому что ключа могло и не быть) — даже если ключ в этот раз точно есть. Компилятор судит по типу, а не по конкретным данным, а тип scores["Алиса"] — всегда Int?, не Int.

Как починить: либо изменить тип переменной на Int? — val aliceScore: Int? = scores["Алиса"], — либо использовать getOrDefault("Алиса", 0) или оператор !! (разберём в будущей главе про null-безопасность), если ты точно уверен, что ключ есть.

KOTLIN · Map[ключ] ВОЗВРАЩАЕТ Int?, НЕ IntInt82значение есть всегдаInt?82 или nullключа могло не быть — тип честно предупреждаеткомпилятор не пустит Int? туда, где обещан гарантированный Int
Обращение к Map по ключу возвращает Int?, а не Int: значение может быть, а может — не быть, и тип честно об этом предупреждает.
Отвечено вопросов: 0 из 2
Компилятор сказал: Unresolved reference 'add'. Что произошло?
val score: Int = scores["Богдан"] не компилируется. Почему?
Отвечено верно: 0 из 2

Символы главы​

Символ / конструкцияКак называетсяЧто делаетПример
listOf / mutableListOfсоздание спискапервый — только для чтения, второй — с методами add и removelistOf("Алиса", "Богдан")
[индекс]оператор индексациидостаёт элемент списка по позиции, начиная с нуляnames[1]
for (x in coll) { }цикл по коллекцииприсваивает x каждый элемент коллекции по очередиfor (name in names)
.filter { } / .map { }преобразованияотбирает элементы по условию и превращает каждый в другое значениеteam.filter { it.score >= 75 }
mapOf / mutableMapOfсоздание картыхранилище пар ключ — значениеmapOf("Алиса" to 82)
toинфиксная функциясоздаёт пару ключ-значение (Pair) для mapOf"Алиса" to 82
[ключ]чтение из картывозвращает значение или null, если ключа нет — поэтому тип с вопросомscores["Алиса"]
[ключ] = значениеиндексация с присваиваниемдобавляет или заменяет значение в MutableMap по ключуscores["Соня"] = 77
for ((k, v) in map)обход карты с деструктуризациейраскладывает каждую пару на ключ и значениеfor ((name, score) in scores)
setOf / mutableSetOfсоздание множествахранит только уникальные значения, порядок не гарантированsetOf("Самара", "Самара")
Углубиться

List — это интерфейс, а не гарантия неизменности. listOf(...) возвращает объект, у типа List которого просто нет методов изменения — но это ограничение на уровне доступа через этот тип, а не свойство самих данных в памяти. Если тот же набор элементов виден откуда-то ещё и через MutableList (например, mutableListOf присвоили переменной типа List), изменить его всё ещё можно — просто не через ссылку List. Для настоящей неизменности данных в памяти в Kotlin есть отдельные структуры за пределами базового курса.

Почему Set не хранит порядок. Внутри он устроен на хеш-таблице: значение попадает в «корзину» по своему hashCode(), и порядок обхода определяется расположением корзин, а не порядком добавления. Если порядок нужен — есть отдельный вид множества, который его запоминает.

Дальше по теме:

  • Collections overview — официальная документация: List, Set, Map и их изменяемые версии.
  • Collection operations — полный список преобразований, включая filter, map, groupBy и associate.
  • Map-specific operations — чтение с значением по умолчанию, обход, слияние карт.

Слова главы​

collection
1 / 8

Потренируйся печатать​

Цель: скорость ≥ 120 зн/мин, точность ≥ 90%

val scores = mapOf("Алиса" to 82, "Богдан" to 91)

Челлендж ⭐​

Команда Competitor уже знакома: Алиса — 82 очка, Богдан — 91, Соня — 66.

Ступень 1. Организаторам нужен список имён тех, кто прошёл отбор — набрал score >= 75. Напиши одно выражение на цепочке .filter { }.map { } (по образцу из этой главы и главы 1), которое из списка team отбирает нужных участников и оставляет только их имена.

Ступень 2 ⭐. Напиши функцию medalFor(score: Int): String, использующую when с диапазонами (>= 90 — "Золото", >= 75 — "Серебро", >= 50 — "Бронза", иначе — "Участие"), и выведи для каждого участника команды строку вида Богдан: Золото.

fun main() { val team = listOf( Competitor("Алиса", 82), Competitor("Богдан", 91), Competitor("Соня", 66) ) // ступень 1: замени emptyList() на список имён с score >= 75 val qualified = emptyList<String>() println("Прошли отбор: $qualified") // ступень 2 (со звёздочкой): допиши medalFor и выведи медаль каждого участника } data class Competitor(val name: String, var score: Int) // ступень 2 (со звёздочкой): допиши функцию здесь // fun medalFor(score: Int): String = when { ... }
Решение (сначала попробуй сам)

Ступень 1:

val qualified = team.filter { it.score >= 75 }.map { it.name }

Ступень 2 ⭐:

fun medalFor(score: Int): String = when {
score >= 90 -> "Золото"
score >= 75 -> "Серебро"
score >= 50 -> "Бронза"
else -> "Участие"
}

fun main() {
val team = listOf(
Competitor("Алиса", 82),
Competitor("Богдан", 91),
Competitor("Соня", 66)
)
val qualified = team.filter { it.score >= 75 }.map { it.name }
println("Прошли отбор: $qualified")
for (competitor in team) {
println("\${competitor.name}: \${medalFor(competitor.score)}")
}
}

data class Competitor(val name: String, var score: Int)

Полный вывод:

Прошли отбор: [Алиса, Богдан]
Алиса: Серебро
Богдан: Золото
Соня: Бронза

Алиса (82) и Богдан (91) прошли отбор — оба набрали 75 и больше; Соня с 66 — нет, поэтому в qualified её не будет, но when всё равно посчитает ей медаль «Бронза» (66 ≥ 50) при выводе полного списка.

Куда дальше: проверенные ресурсы​

Русские материалы с нуля​

Официальный тур Kotlin (на английском)​

  • Kotlin tour: Classes — официальный тур: конструкторы, свойства и методы со встроенными упражнениями.
  • Kotlin tour: Collections — официальный тур: List, Set, Map и их изменяемые версии на практических примерах.

Потренироваться​

play.kotlinlang.org
Та же песочница, что работает в кнопках Run этой главы, но во весь экран.

Сводная проверка по всей главе: 6 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Что должен уметь​

  • Отличать List от MutableList и Map от MutableMap по набору доступных методов, объясняя, почему .add() не работает на List.
  • Создавать и обходить List, Map и Set, используя [индекс]/[ключ], for (x in coll) и деструктуризацию for ((k, v) in map).
  • Строить пары ключ-значение через инфиксную функцию to и безопасно читать значение из карты через getOrDefault или ?:.
  • Объяснять, почему обращение к Map по ключу даёт тип с вопросом, и чинить ошибку компиляции, которая из этого следует.
  • Объяснять, почему Set отбрасывает повторы и не даёт доступа по номеру.
  • Собирать цепочку .filter { }.map { } и говорить, что делает каждое звено.

Видео по теме

ВидеоУрок 9: Коллекции: списки (List). Функции для работы с коллекциями – Котлин курс с нуляKotlin & Android с нуля – Иван Ветров
ВидеоУчим Kotlin с нуля | №24 Коллекции. list.SyntaxSugarDaddy
ВидеоУчим Kotlin с нуля | №26 Коллекции. Map.SyntaxSugarDaddy
ВидеоУчим Kotlin с нуля | №27 Коллекции. Set.SyntaxSugarDaddy
ВидеоМножества (Set) и словари (Map) в KotlinKotlin.forEach

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.