Глиф Учебники

Урок 8. Коллекции — List, MutableList, Set и Map

Курс «Kotlin» · урок 8 из 10 · 3 минуты чтения

Одна переменная хранит одно значение. Когда значений много — оценки класса, список покупок, телефоны друзей, — их кладут в коллекцию. В Kotlin три основные коллекции: список (List), множество (Set) и словарь (Map).

Список: listOf

Список — значения по порядку, у каждого есть номер (индекс). Нумерация начинается с нуля:

Kotlin
fun main() {
    val fruits = listOf("apple", "banana", "cherry")
    println(fruits)
    println(fruits[0])
    println(fruits.size)
    println(fruits.last())
    println("banana" in fruits)
    println(fruits.indexOf("cherry"))
}
Вывод
[apple, banana, cherry]
apple
3
cherry
true
2

Обращение к несуществующему номеру — fruits[3] в списке из трёх элементов — роняет программу с ошибкой IndexOutOfBoundsException (или похожей ArrayIndexOutOfBoundsException). Безопасно — fruits.getOrNull(3): вернёт null.

val fruits = listOf("apple", "banana", "cherry") 0 1 2 3 "apple" "banana" "cherry" нет fruits[0] fruits.last() fruits[3] IndexOutOfBoundsException fruits.getOrNull(3) → null fruits.size → 3 "banana" in fruits → true
Список fruits из трёх элементов с номерами 0, 1, 2: первый — "apple", last() — "cherry"; номера 3 нет, обращение по нему падает, а getOrNull(3) даёт null.

Изменяемый список: mutableListOf

Список из listOf изменить нельзя: в нём нет add и remove. Для изменяемого списка есть mutableListOf:

Kotlin
fun main() {
    val nums = mutableListOf(5, 3, 8)
    nums.add(1)
    nums.add(0, 10)
    println(nums)
    nums.remove(3)
    println(nums)
    nums.removeAt(0)
    nums[0] = 7
    println(nums)
    println(nums.size)
}
Вывод
[10, 5, 3, 8, 1]
[10, 5, 8, 1]
[7, 8, 1]
3
ДействиеЧто делает
add(x)добавить в конец
add(i, x)вставить на место i, остальные сдвигаются
remove(x)удалить первый элемент, равный x
removeAt(i)удалить элемент с номером i
list[i] = xзаменить элемент с номером i
clear()удалить всё

Обратите внимание: список объявлен через val, а меняется. val запрещает положить в переменную другой список, но не менять содержимое этого. Пустой список пишут с типом: val names = mutableListOf<String>() — компилятору иначе неоткуда узнать, что в нём будет.

Перебор списка

Kotlin
fun main() {
    val prices = listOf(120, 45, 300)
    var total = 0
    for (p in prices) {
        total += p
    }
    println(total)
    for (i in prices.indices) {
        println("$i: ${prices[i]}")
    }
}
Вывод
465
0: 120
1: 45
2: 300

prices.indices — диапазон номеров списка, здесь 0..2. Другие способы перебрать с номером — в справке Как перебрать список с индексом.

Операции над списками

Многие задачи со списками решаются без циклов — готовыми функциями. В фигурных скобках им передают лямбду: что сделать с каждым элементом, а сам элемент внутри неё называется it:

Kotlin
fun main() {
    val numbers = listOf(4, 7, 1, 10, 3, 8)
    println(numbers.filter { it % 2 == 0 })
    println(numbers.map { it * it })
    println(numbers.sum())
    println(numbers.max())
    println(numbers.count { it > 5 })
    println(numbers.sorted())
    println(numbers.joinToString(" "))
    println(numbers.filter { it > 3 }.map { it * 10 })
}
Вывод
[4, 10, 8]
[16, 49, 1, 100, 9, 64]
33
10
3
[1, 3, 4, 7, 8, 10]
4 7 1 10 3 8
[40, 70, 100, 80]
ФункцияЧто делает
filter { условие }новый список из элементов, для которых условие верно
map { выражение }новый список: каждый элемент превращён по выражению
sum(), max(), min()сумма, наибольший, наименьший
count { условие }сколько элементов подходит
sorted()новый список по возрастанию
joinToString(" ")все элементы в одну строку через разделитель
any { }, all { }есть ли хоть один подходящий, подходят ли все

Эти функции не меняют исходный список, а возвращают новый — numbers после них прежний. Их можно ставить цепочкой: filter { it > 3 }.map { it * 10 } — сначала отобрать, потом превратить. Как сортировать по-разному — в справке Как отсортировать список.

numbers .filter { it > 3 } .map { it * 10 } 4 4 40 7 7 70 1 10 10 100 3 8 8 80 Каждая операция даёт новый список, numbers остаётся прежним.
Из списка 4, 7, 1, 10, 3, 8 filter { it > 3 } оставляет 4, 7, 10, 8, а map { it * 10 } превращает их в 40, 70, 100, 80.

max() и min() у пустого списка бросают ошибку; если список может быть пустым, берите maxOrNull() — вернёт null.

Множество: Set

Множество хранит значения без повторов, номеров у элементов нет:

Kotlin
fun main() {
    val visited = mutableSetOf("Minsk", "Brest")
    visited.add("Minsk")
    visited.add("Grodno")
    println(visited)
    println(visited.size)
    println("Brest" in visited)
    val unique = listOf(3, 1, 3, 2, 1).toSet()
    println(unique)
}
Вывод
[Minsk, Brest, Grodno]
3
true
[3, 1, 2]

Второй "Minsk" не добавился — он уже есть. toSet() — быстрый способ убрать повторы из списка. Множества из setOf и mutableSetOf выводятся в том порядке, в каком элементы добавлялись.

Словарь: Map

Словарь хранит пары «ключ — значение»: по ключу (имени) быстро находится значение (возраст). Пара пишется через to:

Kotlin
fun main() {
    val ages = mutableMapOf("Anna" to 20, "Oleg" to 25)
    ages["Ivan"] = 18
    ages["Anna"] = 21
    println(ages)
    println(ages["Oleg"])
    println(ages["Petr"])
    println(ages.getOrDefault("Petr", 0))
    for ((name, age) in ages) {
        println("$name: $age")
    }
}
Вывод
{Anna=21, Oleg=25, Ivan=18}
25
null
0
Anna: 21
Oleg: 25
Ivan: 18

Неизменяемый словарь — mapOf(...), изменяемый — mutableMapOf(...), как и у списков.

Set — без повторов visited "Minsk" "Brest" "Grodno" add("Minsk") уже есть add("Grodno") добавлен Map — ключ → значение ages "Anna" 21 было 20 "Oleg" 25 "Ivan" 18 ages["Petr"] null ключа нет
Множество visited не принимает второй "Minsk", но добавляет "Grodno"; в словаре ages у ключа "Anna" значение 21 заменило 20, а по ключу "Petr" получается null.

Массивы

В Kotlin есть и массивы — arrayOf(1, 2, 3), IntArray(5) (пять нулей). Размер массива задаётся при создании и не меняется. Для учебных задач обычно удобнее списки. Если всё же выводите массив, превратите его в текст через contentToString(): println(arr) напечатает не числа, а служебную запись вида [I@1b6d3586.

Коротко

Проверьте себя

1. Что выведет программа?
Kotlin
fun main() {
    val list = listOf(10, 20, 30)
    println(list[1])
}
2. Почему listOf(1, 2).add(3) не собирается?
3. Что даст listOf(1, 2, 3, 4).filter { it > 2 }?
4. Что вернёт mapOf("a" to 1)["b"]?
5. Что выведет println(setOf(2, 2, 3))?