Освой Kotlin играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Kotlin Immutable Collections library
Коллекции в Kotlin используют множество различных интерфейсов: Iterable, Collection, Set, List, MutableIterable, MutableCollection и др.
В Kotlin нет собственных коллекций, только Java-коллекции. Но при этом в Kotlin они обладают более широкими возможностями, используя расширения. Например, вы можете узнать последний элемент списка или найти максимальное значение в коллекции чисел.
Важная особенность - в Kotlin интерфейсы явно разделены на две группы - изменяемые и неизменяемые. Старайтесь всегда использовать неизменяемые коллекции с доступом для чтения. Если вам нужно изменять коллекцию, то тогда выбирайте другой вариант. Здесь не надо путать с var и val. Если вы создадите список для чтения и привяжете его к переменной var, список всё равно нельзя будет изменить после создания.
Для создания различных типов коллекций есть разные функции.
Функция associate() позволяет получить Map, используя поля класса как ключ и значение. Например, в нашем случае имена станут ключами, а возраст - ассоциированными значениями.
val cats = listOf(
Cat("Barsik", 5, 1),
Cat("Murzik", 9, 2)
)
val associatedMap = cats.associate { Pair(it.name, it.age) }
println(associatedMap)
// Результат
{Barsik=5, Murzik=9}
Функция associateTo() - Зададим отображение originalMap, где ключ является строкой, а значением число. Берём список и применяем наше отображение, указывая нужные поля класса (имя и вес).
val originalMap = mutableMapOf<String, Int>()
cats.associateTo(originalMap) { it.name to it.weight }
println(originalMap)
// Результат
{Barsik=1, Murzik=2}
Функция flatMap() работает с коллекцией, содержащей коллекции, и возвращает объединённую «плоскую» коллекцию, содержащую все элементы исходных коллекций.
val result = listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatMap { it }
println(result) // [1, 2, 3, 4, 5, 6]
Функция map() проходит по каждому элементу и делает операции с элементом в лямбде-выражении, объединяя результаты в новую коллекцию.

Умножаем каждый элемент списка на себя.
var list = listOf(1, 2, 3)
var dest = list.map { it * it }
println(dest.toString()) // выводит [1, 4, 9]
Выводим только имена, игнорируя возраст и другие поля класса.
val cats = listOf(Cat("Barsik", 5, true),
Cat("Murzik", 9, true))
println(cats.map (Cat::name))
// или println(cats.map {it.name})
Можно объединить вызовы функций в цепочки. Выводим имена котов, чей возраст больше 5.
val cats = listOf(Cat("Barsik", 5, true),
Cat("Murzik", 9, true))
println(cats.filter {it.age > 5}.map (Cat::name))
Удаляем префикс у строк списка.
val cats = listOf("!Мурзик", "!Рыжик", "Барсик")
val shortCats = cats.map { name ->
name.removePrefix("!")
}
println(shortCats)
Если при переборе нужна информация об индексах, то используйте mapIndexed().

var list = listOf(8, 4, 2)
var dest = list.mapIndexed { index, i -> "$index: $i"}
println(dest.toString()) // выводит [0: 8, 1: 4, 2: 2]
Если нужно вернуть коллекцию без элементов null, то используйте mapNotNull().
val list = listOf("Барсик", null, "Васька", null, "Мурзик")
val dest = list.mapNotNull { it?.length }
println(dest.toString()) // выводит [6, 6, 6]
val set = setOf("Барсик", null, "Васька", null, "Мурзик")
val newSet = set.mapNotNull { it?.last() }
println(newSet.toString()) // выводит [к, а, к]
Также есть функция mapIndexedNotNull().
Функция-комбинатор zip() принимает разные коллекции и объединяют их в одну новую.

Для примера объединим два списка: с именами котов и их размерами. Функция zip() возвращает новый список (коллекцию пар Pair). Для этой коллекции пар вызовем функцию toMap(), чтобы получить ассоциативный массив, к элементам которого можно обращаться по ключу. В этом случае ключ — имя кота.
val names = listOf("Барсик", "Мурзик", "Рыжик")
val sizes = listOf("большой", "средний", "совсем котёнок")
val catMap = names.zip(sizes).toMap()
// обращаемся к ключу
println(catMap["Рыжик"]) // совсем котёнок
Функция unzip() является полной противоположностью функции zip(). Если у нас есть список пар, то функция разобьёт пары на два списка - в первом будут первые элементы пары, во втором будут вторые элементы каждой пары.

val pairs = listOf(Pair("Россия", "рубль"), Pair("США", "доллар"), Pair("Украина", "гривна"))
println(pairs.unzip().toString()) // выводит ([Россия, США, Украина], [рубль, доллар, гривна])
Хотя мы считаем, что стандартные коллекции, создаваемые через listOf(), setOf(), mapOf(), являются коллекция только для чтения, они в реальности не совсем неизменяемые. Мы можем изменить их следующим образом.
val list = mutableListOf("A", "B", "C")
val readOnlyList: List<String> = list
list.add("D") // Modifies the original list
println(readOnlyList) // Output: [A, B, C, D]
(readOnlyList as MutableList).add("E")
println(readOnlyList) // Output: [A, B, C, D, E]
Чтобы получить реальные неизменяемые коллекции, была разработана специальная библиотека Kotlin Immutable Collections library.
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
Создавать неизменяемые коллекции можно следующим образом.
import kotlinx.collections.immutable.*
val immutableList = persistentListOf("A", "B", "C")
val immutableSet = persistentSetOf(1, 2, 3)
val immutableMap = persistentMapOf("key1" to 100, "key2" to 200)
Если мы попытаемся модифицировать содержимое коллекций, то будет создана новая коллекция, а старая останется без изменений.
val newList = immutableList.add("D") // Creates a new list
println(newList) // Output: [A, B, C, D]
val newMap = immutableMap.put("key3", 300)
println(newMap) // Output: {key1=100, key2=200, key3=300}
Неизменяемые коллекции хорошо работают в Compose, позволяя избежать рекомпозиции.
// Обычный подход
@Composable
fun MyListScreen(items: List<String>) {
LazyColumn {
items(items) { item ->
Text(text = item)
}
}
}
// Другой подход
val items = remember { persistentListOf("A", "B", "C") }
MyListScreen(items)