Урок по треку Kotlin 7 мин

Коллекции и последовательности за 7 минут

Read-only и mutable, map, filter, groupBy, fold, Sequence и ленивость.

Коллекции это половина повседневного Kotlin-кода. На собесе проверяют три вещи: разницу read-only и mutable, знание операторов (map, groupBy, fold, associate) и понимание, когда нужна Sequence.

Read-only не значит immutable

val list: List<Int> = listOf(1, 2, 3)          // read-only интерфейс
val mutable: MutableList<Int> = mutableListOf(1, 2)
mutable.add(3)

List в Kotlin это интерфейс только для чтения: у него нет add. Но это не гарантия неизменяемости. Под ним может лежать ArrayList, который кто-то меняет через ссылку MutableList, или его можно привести обратно as MutableList. На JVM List и MutableList компилируются в один и тот же java.util.List. Настоящие неизменяемые коллекции дают библиотека kotlinx.collections.immutable (persistentListOf) или копия при отдаче наружу.

Иерархия: Iterable → Collection → List / Set; Map отдельно. У каждого есть Mutable-версия.

Фабрики: listOf, mutableListOf, arrayListOf, setOf (сохраняет порядок вставки, это LinkedHashSet), hashSetOf, mapOf (тоже LinkedHashMap), buildList { add(1) }, List(5) { it * it }.

Основные операции

val orders = listOf(Order("Аня", 500), Order("Борис", 300), Order("Аня", 200))

orders.map { it.sum }                        // [500, 300, 200]
orders.filter { it.sum > 250 }
orders.sumOf { it.sum }                      // 1000
orders.groupBy { it.user }                   // Map<String, List<Order>>
orders.groupingBy { it.user }.eachCount()    // {Аня=2, Борис=1}
orders.associateBy { it.user }               // последний побеждает: Аня -> Order(Аня, 200)
orders.associate { it.user to it.sum }       // Map из пар
orders.partition { it.sum > 250 }            // Pair(подходящие, остальные)
orders.sortedByDescending { it.sum }
orders.maxByOrNull { it.sum }
orders.distinctBy { it.user }
orders.chunked(2); orders.windowed(2)
listOf(listOf(1), listOf(2, 3)).flatten()
orders.flatMap { listOf(it.user, it.user) }
listOf(1, 2, 3).zip(listOf("a", "b"))         // [(1, a), (2, b)]

fold(initial) { acc, x -> } сворачивает коллекцию в одно значение с начальным аккумулятором. reduce берёт первый элемент как начальный и падает на пустой коллекции.

Для Map: getOrDefault, getOrPut (для MutableMap), mapValues, filterKeys, withDefault. Обращение map[key] возвращает nullable, getValue(key) бросает исключение при отсутствии ключа.

Жадность и Sequence

Операции над Iterable жадные: каждая создаёт новый список целиком.

val result = (1..1_000_000)
    .map { it * 2 }          // список на миллион
    .filter { it % 3 == 0 }  // ещё один список
    .first()

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

val result = (1..1_000_000).asSequence()
    .map { it * 2 }
    .filter { it % 3 == 0 }
    .first()        // обработано всего 3 элемента

Операции делятся на промежуточные (map, filter, take, возвращают Sequence) и терминальные (toList, first, sum, forEach). Без терминальной ничего не выполнится.

Когда брать Sequence: длинные цепочки на больших коллекциях, ранний выход (first, take), бесконечные последовательности (generateSequence). Когда не брать: маленькие коллекции и одна-две операции, там список быстрее (у последовательности накладные расходы на итераторы и лямбды). sorted на последовательности всё равно соберёт всё в память.

Это аналог Java Stream, но однопоточный и без parallel().

Массивы

Array<Int> хранит объекты Integer, IntArray примитивы int[]. Для производительных задач нужны примитивные массивы. Сравнение массивов через == сравнивает ссылки; по содержимому contentEquals.

Частые ошибки

  • Считать List неизменяемым.
  • associateBy при дублях ключей молча оставляет последний элемент.
  • first() и reduce на пустой коллекции бросают исключение; безопасно firstOrNull(), reduceOrNull.
  • Изменение коллекции во время обхода for даёт ConcurrentModificationException; удалять через removeAll { } или iterator.remove().
  • asSequence() на списке из 10 элементов ради «оптимизации».

Что спросят на собесе

  • Чем List отличается от MutableList и неизменяемого списка?
  • Чем Sequence отличается от Iterable и когда её использовать?
  • map против flatMap, fold против reduce, groupBy против associateBy.
  • В каком порядке выполнятся map и filter для списка и для последовательности?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.