Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,234 @@
---
title: Коллекции
description: Обзор иерархии коллекций Kotlin, разделение на читаемые и изменяемые интерфейсы
tags:
- kotlin
- collections
---
Коллекции Kotlin - это **те же** классы JDK (`ArrayList`, `HashMap`, `LinkedHashSet`), но с другими интерфейсами поверх них. Главное отличие от Java: изменяемость выражена в системе типов.
```kotlin
fun main() {
val readOnly: List<Int> = listOf(1, 2, 3)
val mutable: MutableList<Int> = mutableListOf(1, 2, 3)
mutable.add(4)
// readOnly.add(4) // ошибка: у List нет метода add
println("$readOnly / $mutable") // [1, 2, 3] / [1, 2, 3, 4]
println(readOnly.javaClass.name) // java.util.Arrays$ArrayList
println(mutable.javaClass.name) // java.util.ArrayList
}
```
## Иерархия
```mermaid
graph TD
I["Iterable&lt;out T&gt;"] --> C["Collection&lt;out T&gt;"]
C --> L["List&lt;out T&gt;"]
C --> S["Set&lt;out T&gt;"]
I --> MI["MutableIterable&lt;T&gt;"]
MI --> MC["MutableCollection&lt;T&gt;"]
C --> MC
MC --> ML["MutableList&lt;T&gt;"]
L --> ML
MC --> MS["MutableSet&lt;T&gt;"]
S --> MS
M["Map&lt;K, out V&gt;"] --> MM["MutableMap&lt;K, V&gt;"]
```
Два важных факта из этой схемы:
- `MutableList` **наследует** `List`, то есть изменяемую коллекцию можно передать туда, где ожидается читаемая. Обратное невозможно.
- `Map` не входит в `Collection` - это отдельная иерархия, как и в Java.
| Интерфейс | Чтение | Изменение | Реализация по умолчанию |
|---|---|---|---|
| `List<T>` | да | нет | `ArrayList` под капотом |
| `MutableList<T>` | да | да | `ArrayList` |
| `Set<T>` | да | нет | `LinkedHashSet` |
| `MutableSet<T>` | да | да | `LinkedHashSet` |
| `Map<K, V>` | да | нет | `LinkedHashMap` |
| `MutableMap<K, V>` | да | да | `LinkedHashMap` |
Обратите внимание: умолчание для `Set` и `Map` - **`Linked`**-версии, то есть порядок вставки сохраняется. В Java `new HashSet<>()` порядок не гарантирует.
## Создание
```kotlin
fun main() {
val a = listOf(1, 2, 3) // read-only
val b = mutableListOf(1, 2, 3) // изменяемый
val c = emptyList<Int>() // пустой
val d = listOfNotNull(1, null, 3) // null отфильтрованы
val e = List(3) { it * it } // по индексу
println("$a $b $c $d $e") // [1, 2, 3] [1, 2, 3] [] [1, 3] [0, 1, 4]
val s = setOf(1, 2, 2, 3) // дубликаты убраны
val m = mapOf("a" to 1, "b" to 2)
println("$s $m") // [1, 2, 3] {a=1, b=2}
}
```
Специализированные варианты для разных реализаций:
```kotlin
fun main() {
val sorted = sortedSetOf(3, 1, 2) // TreeSet
val sortedMap = sortedMapOf("b" to 2, "a" to 1)
val linked = linkedSetOf(3, 1, 2) // LinkedHashSet
val hash = hashSetOf(3, 1, 2) // HashSet, порядок не гарантирован
val arrayDeque = ArrayDeque(listOf(1, 2, 3))
println("$sorted $sortedMap $linked") // [1, 2, 3] {a=1, b=2} [3, 1, 2]
println(hash.size) // 3
arrayDeque.addFirst(0)
println(arrayDeque) // [0, 1, 2, 3]
}
```
## Билдеры
Когда коллекцию нужно собрать по условию, а результат отдать как read-only:
```kotlin
fun main() {
val list = buildList {
add(1)
if (true) add(2)
addAll(listOf(3, 4))
}
println(list) // [1, 2, 3, 4]
val map = buildMap {
put("a", 1)
putAll(mapOf("b" to 2))
}
println(map) // {a=1, b=2}
val set = buildSet { repeat(3) { add(it % 2) } }
println(set) // [0, 1]
}
```
Внутри лямбды коллекция изменяемая, снаружи возвращается `List<T>`. Это чище, чем `mutableListOf` с последующим приведением.
## Пустая коллекция и null
```kotlin
fun main() {
val nullable: List<Int>? = null
println(nullable?.size ?: 0) // 0
println(nullable.orEmpty()) // []
println(nullable.isNullOrEmpty()) // true
val empty = emptyList<Int>()
println(empty.firstOrNull()) // null
println(empty.sum()) // 0
}
```
Практическое правило: в API возвращайте пустую коллекцию, а не `null`. `List<T>?` заставляет каждого клиента писать `?.` или `orEmpty()`, не давая взамен никакой информации - пустой список уже означает «ничего нет».
## Массив не коллекция
```kotlin
fun main() {
val array = arrayOf(1, 2, 3)
// array.filter — есть, потому что Array получает расширения из Iterable
println(array.filter { it > 1 }) // [2, 3] — это уже List
println(array.toList()::class.simpleName) // ArrayList
println(listOf(1, 2).toTypedArray().size) // 2
}
```
`Array<T>` - отдельный тип, не входящий в `Collection`. У него фиксированный размер и инвариантность. Подробнее: [Массивы](../Синтаксис%20и%20типы/Работа%20с%20данными/Массивы.md)
## Карта раздела
| Заметка | О чём |
|---|---|
| [List, Set, Map](List,%20Set,%20Map.md) | три семейства, их гарантии и выбор между ними |
| [Изменяемость](Изменяемость.md) | read-only против immutable, защита от изменения |
| [Операции](Операции.md) | `map`, `filter`, `groupBy`, `fold` и остальной арсенал |
| [Sequence](Sequence.md) | ленивые вычисления и когда они выигрывают |
| [Коллекции под капотом](Коллекции%20под%20капотом.md) | что лежит в байт-коде, сложность операций, память |
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `new ArrayList<>()` | `mutableListOf()` |
> | `List.of(1, 2)` (Java 9+) | `listOf(1, 2)` |
> | `Collections.emptyList()` | `emptyList()` |
> | изменяемость не в типе | `List` против `MutableList` |
> | `UnsupportedOperationException` в рантайме | ошибка компиляции |
> | `Stream` для операций | расширения прямо на коллекции |
> | `new HashSet<>()` - порядок не гарантирован | `setOf()` - порядок вставки |
>
> **Разделение на `List` и `MutableList` - главное нововведение.** В Java `List` - один интерфейс с методом `add`, и `List.of(1, 2)` его тоже имеет: вызов просто бросает `UnsupportedOperationException` в рантайме.
> ```java
> List<Integer> list = List.of(1, 2);
> list.add(3); // компилируется, падает в рантайме
> ```
> ```kotlin
> fun main() {
> val list = listOf(1, 2)
> // list.add(3) // не компилируется
> println(list.size)
> }
> ```
> Ошибка переехала из рантайма в компиляцию - это главная практическая польза.
>
> **Операции не требуют `Stream`.** В Java цепочка выглядит как `list.stream().filter(...).map(...).collect(Collectors.toList())`. В Kotlin `filter` и `map` - функции-расширения прямо на `Iterable`, они сразу возвращают `List`:
> ```kotlin
> fun main() {
> println(listOf(1, 2, 3).filter { it > 1 }.map { it * 10 }) // [20, 30]
> }
> ```
> Цена - промежуточный список на каждом шаге. Ленивый аналог `Stream` - это [Sequence](Sequence.md).
>
> **`setOf` и `mapOf` сохраняют порядок вставки**, потому что под ними `LinkedHashSet` и `LinkedHashMap`. Это удобно (вывод предсказуем), но на порядок всё равно не стоит полагаться в контракте: тип объявлен как `Set`, а не как «упорядоченное множество».
>
> Сравнить: [HashMap в Java](../../Программирование%20на%20Java/Коллекции/HashMap.md)
> [!warning] Подводные камни
> **`List` - это read-only, а не immutable.** Объект под ним может меняться через другую ссылку:
> ```kotlin
> fun main() {
> val mutable = mutableListOf(1, 2)
> val readOnly: List<Int> = mutable // та же коллекция!
> mutable.add(3)
> println(readOnly) // [1, 2, 3] — изменился
> }
> ```
> `List` гарантирует только то, что **вы** через эту ссылку не измените коллекцию. Подробнее: [Изменяемость](Изменяемость.md)
>
> **Из Java любой `List` изменяем.** Типы Kotlin-коллекций - это `java.util.List`, поэтому Java-код про read-only не знает:
> ```kotlin
> fun giveList(): List<Int> = mutableListOf(1, 2)
>
> fun main() { println(giveList().size) } // 2
> ```
> Java-клиент вызовет `add` без всяких ошибок компиляции, и если под ссылкой `ArrayList` - элемент добавится. Для настоящей защиты нужна копия.
>
> **`listOf()` возвращает разные реализации.** `listOf()` без аргументов - `EmptyList`, `listOf(x)` - `SingletonList`, с несколькими - `Arrays$ArrayList`. То же у `mapOf` и `setOf`: на одном элементе это `SingletonMap`/`SingletonSet`, а не `Linked`-версия. Последний не поддерживает `add` даже через каст:
> ```kotlin
> fun main() {
> val list = listOf(1, 2)
> try {
> @Suppress("UNCHECKED_CAST")
> (list as MutableList<Int>).add(3)
> } catch (e: UnsupportedOperationException) {
> println("каст скомпилировался, но add упал")
> }
> }
> ```
> Приводить `List` к `MutableList` нельзя: иногда это сработает, иногда бросит исключение.
>
> **`Map` не `Collection`** - у него нет `size` через `Collection`-интерфейс, `filter` работает с `Map.Entry`, а `map` возвращает `List`:
> ```kotlin
> fun main() {
> val m = mapOf("a" to 1, "b" to 2)
> println(m.filter { it.value > 1 }) // {b=2} — остался Map
> println(m.map { it.key }) // [a, b] — стал List
> println(m.mapValues { it.value * 10 }) // {a=10, b=20}
> }
> ```
>
> **Операции создают новую коллекцию на каждом шаге.** Цепочка из пяти `map`/`filter` по списку в миллион элементов создаст пять списков. Для длинных цепочек и больших данных нужен [Sequence](Sequence.md).
>
> **`hashSetOf` и `hashMapOf` не гарантируют порядок.** Если тест полагается на порядок обхода, он может сломаться при смене JDK. Используйте `setOf`/`mapOf` или сортируйте явно.
Далее: [List, Set, Map](List,%20Set,%20Map.md)