Освой 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(), associateTo(), associateBy()

Функция 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()

Функция flatMap() работает с коллекцией, содержащей коллекции, и возвращает объединённую «плоскую» коллекцию, содержащую все элементы исходных коллекций.


val result = listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatMap { it }
println(result) // [1, 2, 3, 4, 5, 6]

Функция map()

Функция map() проходит по каждому элементу и делает операции с элементом в лямбде-выражении, объединяя результаты в новую коллекцию.

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()

Если при переборе нужна информация об индексах, то используйте mapIndexed().

mapIndexed()


var list = listOf(8, 4, 2)
var dest = list.mapIndexed { index, i ->  "$index: $i"}
println(dest.toString()) // выводит [0: 8, 1: 4, 2: 2]

Функция mapNotNull()

Если нужно вернуть коллекцию без элементов 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() принимает разные коллекции и объединяют их в одну новую.

zip()

Для примера объединим два списка: с именами котов и их размерами. Функция zip() возвращает новый список (коллекцию пар Pair). Для этой коллекции пар вызовем функцию toMap(), чтобы получить ассоциативный массив, к элементам которого можно обращаться по ключу. В этом случае ключ — имя кота.


val names = listOf("Барсик", "Мурзик", "Рыжик")
val sizes = listOf("большой", "средний", "совсем котёнок")
val catMap = names.zip(sizes).toMap()
// обращаемся к ключу
println(catMap["Рыжик"]) // совсем котёнок

unzip()

Функция unzip() является полной противоположностью функции zip(). Если у нас есть список пар, то функция разобьёт пары на два списка - в первом будут первые элементы пары, во втором будут вторые элементы каждой пары.

unzip()


val pairs = listOf(Pair("Россия", "рубль"), Pair("США", "доллар"), Pair("Украина", "гривна"))
println(pairs.unzip().toString()) // выводит ([Россия, США, Украина], [рубль, доллар, гривна])

Kotlin Immutable Collections library

Хотя мы считаем, что стандартные коллекции, создаваемые через 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)
Реклама