Как работают неизменяемые и изменяемые коллекции в Kotlin?

В Kotlin коллекции делятся на неизменяемые (immutable) и изменяемые (mutable). Это разделение реализовано через разные интерфейсы и фабричные функции, и оно играет важную роль в проектировании безопасного и предсказуемого кода.

Неизменяемые коллекции

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

  • List<T> — упорядоченный список элементов, допускающий дубликаты.

  • Set<T> — набор уникальных элементов без определённого порядка.

  • Map<K, V> — отображение ключей на значения, где ключи уникальны.

Примеры:

val numbers: List&lt;Int&gt; = listOf(1, 2, 3)
val names: Set&lt;String&gt; = setOf("Alice", "Bob")
val ages: Map&lt;String, Int&gt; = mapOf("Tom" to 30, "Sue" to 25)

Попытка изменить такие коллекции приведёт к ошибке компиляции:

numbers.add(4) // Ошибка: Unresolved reference: add
ages\["Tom"\] = 35 // Ошибка: нельзя изменить

Изменяемые коллекции

Изменяемые коллекции позволяют изменять содержимое: добавлять, удалять и изменять элементы. Они реализуют интерфейсы:

  • MutableList<T> — изменяемый список.

  • MutableSet<T> — изменяемый набор.

  • MutableMap<K, V> — изменяемое отображение.

Создаются с помощью функций mutableListOf(), mutableSetOf(), mutableMapOf():

val numbers: MutableList&lt;Int&gt; = mutableListOf(1, 2, 3)
numbers.add(4) // OK
val names: MutableSet&lt;String&gt; = mutableSetOf("Alice", "Bob")
names.remove("Alice") // OK
val ages: MutableMap&lt;String, Int&gt; = mutableMapOf("Tom" to 30)
ages\["Tom"\] = 35 // OK
ages\["Sue"\] = 28 // OK

Интерфейсы и подтипизация

  • MutableList наследует List, MutableSet — Set, MutableMap — Map.

  • Это значит, что MutableList может использоваться там, где ожидается List.

  • Однако обратное недопустимо: List нельзя привести к MutableList.

Пример:

fun printList(list: List&lt;String&gt;) {
println(list.joinToString())
}
val mutable = mutableListOf("A", "B")
printList(mutable) // OK  полиморфизм работает
fun addToList(list: MutableList&lt;String&gt;) {
list.add("C")
}
val readOnly = listOf("A", "B")
// addToList(readOnly) // Ошибка: List не является MutableList

Скрытие мутабельности

Можно использовать изменяемую коллекцию, но скрыть её за интерфейсом неизменяемой:

val mutableList = mutableListOf(1, 2, 3)
val readOnly: List&lt;Int&gt; = mutableList
mutableList.add(4)
println(readOnly) // \[1, 2, 3, 4\]  readOnly "видит" изменения

Это показывает, что неизменяемость в Kotlin не означает иммутабельность объекта, а лишь ограничение доступа к методам изменения через тип интерфейса.

Реально неизменяемые коллекции

Если нужна настоящая иммутабельность, нужно создавать копии:

val original = listOf(1, 2, 3)
val newList = original + 4 // возвращает новый список
println(original) // \[1, 2, 3\]
println(newList) // \[1, 2, 3, 4\]

Оператор + создаёт новый объект коллекции, не изменяя исходную.

Методы изменения

MutableList:

  • add(element), add(index, element)

  • remove(element), removeAt(index)

  • set(index, element)

MutableSet:

  • add(element)

  • remove(element)

  • clear()

MutableMap:

  • put(key, value) или map[key] = value

  • remove(key)

  • putAll(otherMap)

Методы только чтения

List:

  • get(index)

  • size

  • contains(element)

  • indexOf(element)

Set:

  • contains(element)

  • size

  • isEmpty()

Map:

  • get(key) или map[key]

  • keys, values, entries

  • containsKey(key), containsValue(value)

Преобразование между типами

val readOnly = listOf(1, 2, 3)
val mutable = readOnly.toMutableList()
mutable.add(4)
val newReadOnly = mutable.toList()

Функции toList(), toSet(), toMap() создают копии, а не просто обёртки.

Использование с val и var

Объявление коллекции с val означает, что переменная не может указывать на другой объект, но содержимое изменяемой коллекции можно менять:

val list = mutableListOf(1, 2, 3)
list.add(4) // OK
// list = mutableListOf(5) // Ошибка: нельзя переназначить переменную
var list2 = listOf(1, 2, 3)
list2 = listOf(4, 5, 6) // OK  переменная указывает на новый объект

Коллекции в многопоточности

Неизменяемые коллекции безопасны для многопоточности, потому что их нельзя изменить. Изменяемые коллекции требуют синхронизации или специальных структур:

val list = Collections.synchronizedList(mutableListOf&lt;String&gt;())

Или использовать потокобезопасные структуры из java.util.concurrent или kotlinx.coroutines.

Специальные коллекции

  • emptyList(), emptySet(), emptyMap() — создание пустых коллекций.

  • listOfNotNull(...) — фильтрация null-значений.

  • mapOf(...), mutableMapOf(...) — пары key to value.

  • arrayListOf(), linkedSetOf() — специфические реализации.

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