Как работают неизменяемые и изменяемые коллекции в Kotlin?
В Kotlin коллекции делятся на неизменяемые (immutable) и изменяемые (mutable). Это разделение реализовано через разные интерфейсы и фабричные функции, и оно играет важную роль в проектировании безопасного и предсказуемого кода.
Неизменяемые коллекции
Неизменяемые коллекции — это такие коллекции, содержимое которых нельзя изменить после создания. Нельзя ни добавить, ни удалить, ни изменить элементы. Они доступны через интерфейсы:
-
List<T> — упорядоченный список элементов, допускающий дубликаты.
-
Set<T> — набор уникальных элементов без определённого порядка.
-
Map<K, V> — отображение ключей на значения, где ключи уникальны.
Примеры:
val numbers: List<Int> = listOf(1, 2, 3)
val names: Set<String> = setOf("Alice", "Bob")
val ages: Map<String, Int> = 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<Int> = mutableListOf(1, 2, 3)
numbers.add(4) // OK
val names: MutableSet<String> = mutableSetOf("Alice", "Bob")
names.remove("Alice") // OK
val ages: MutableMap<String, Int> = 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<String>) {
println(list.joinToString())
}
val mutable = mutableListOf("A", "B")
printList(mutable) // OK — полиморфизм работает
fun addToList(list: MutableList<String>) {
list.add("C")
}
val readOnly = listOf("A", "B")
// addToList(readOnly) // Ошибка: List не является MutableList
Скрытие мутабельности
Можно использовать изменяемую коллекцию, но скрыть её за интерфейсом неизменяемой:
val mutableList = mutableListOf(1, 2, 3)
val readOnly: List<Int> = 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<String>())
Или использовать потокобезопасные структуры из java.util.concurrent или kotlinx.coroutines.
Специальные коллекции
-
emptyList(), emptySet(), emptyMap() — создание пустых коллекций.
-
listOfNotNull(...) — фильтрация null-значений.
-
mapOf(...), mutableMapOf(...) — пары key to value.
-
arrayListOf(), linkedSetOf() — специфические реализации.
Kotlin делает чёткое различие между интерфейсами изменяемости и неизменяемости, при этом предоставляя гибкие способы преобразования и безопасного использования коллекций. Это важно для написания надёжного и предсказуемого кода, особенно в многопоточной среде и при использовании функционального подхода.