В чём разница между listOf, mutableListOf, arrayListOf?
В Kotlin коллекции делятся на изменяемые (mutable) и неизменяемые (immutable). При этом существует несколько способов создания списков: listOf, mutableListOf и arrayListOf. Несмотря на то, что все три создают списки, между ними есть важные различия в типах, возможностях изменения содержимого и реализации. Эти различия важны как с точки зрения безопасности типов, так и производительности или взаимодействия с Java-кодом.
listOf(...)
Это функция для создания неизменяемого (immutable) списка. Такой список нельзя модифицировать: нельзя добавлять, удалять или изменять элементы. Однако сами элементы могут быть изменяемыми объектами (если они — ссылки на mutable-объекты).
Сигнатура:
fun <T> listOf(vararg elements: T): List<T>
Пример:
val names = listOf("Alice", "Bob")
Тип: List<String> (интерфейс List, без методов изменения)
Ограничения:
names.add("Charlie") // Ошибка компиляции
names\[0\] = "Ann" // Ошибка компиляции
Важно: listOf создаёт ссылку на список, который нельзя изменить, но если в списке хранятся объекты, то можно менять внутренности этих объектов:
data class User(var name: String)
val users = listOf(User("Tom"))
users\[0\].name = "Tim" // Это разрешено
mutableListOf(...)
Создаёт изменяемый список. Можно добавлять, удалять, изменять элементы. Это наиболее часто используемый тип в практике, особенно когда нужно накапливать или изменять данные.
Сигнатура:
fun <T> mutableListOf(vararg elements: T): MutableList<T>
Пример:
val names = mutableListOf("Alice", "Bob")
names.add("Charlie")
names\[0\] = "Ann"
Тип: MutableList<String> (наследует от List, реализует методы изменения)
Под капотом: возвращает ArrayList<T> (по умолчанию).
arrayListOf(...)
Создаёт конкретный экземпляр ArrayList, которая является реализацией изменяемого списка из стандартной Java-коллекции. Этот список идентичен тому, что создаётся при mutableListOf(), но тип указывается явно.
Сигнатура:
fun <T> arrayListOf(vararg elements: T): ArrayList<T>
Пример:
val names = arrayListOf("Alice", "Bob")
names.add("Charlie")
Тип: ArrayList<String> (подтип MutableList<String>)
Сравнение: listOf vs mutableListOf vs arrayListOf
Свойство | listOf | mutableListOf | arrayListOf |
---|---|---|---|
Изменяемость | Нет | Да | Да |
--- | --- | --- | --- |
Возвращаемый тип | List<T> | MutableList<T> | ArrayList<T> |
--- | --- | --- | --- |
Добавление/удаление элементов | Нельзя | Можно | Можно |
--- | --- | --- | --- |
Присваивание по индексу | Нельзя | Можно | Можно |
--- | --- | --- | --- |
Под капотом | ArrayList<T> (read-only) | ArrayList<T> | ArrayList<T> |
--- | --- | --- | --- |
Уточнение типа | Абстрактный | Интерфейс (mutable) | Конкретный класс |
--- | --- | --- | --- |
Использование с Java API | Ограниченное | Совместимо | Полностью совместимо |
--- | --- | --- | --- |
Когда использовать что
-
listOf — когда вы не хотите допустить изменение списка, особенно при передаче в функцию или при использовании как константы. Это повышает безопасность кода.
-
mutableListOf — когда список должен изменяться: добавление, удаление, изменение по индексу. Хорошо подходит для алгоритмов, сборов, буферов и т. д.
-
arrayListOf — когда требуется именно тип ArrayList, например, для передачи в Java API, где требуется java.util.ArrayList, или когда вы хотите использовать специфические методы ArrayList.
Поведение ссылок и копий
Все три создают списки по ссылке. При копировании списка переменной копируется ссылка, а не содержимое.
val original = mutableListOf(1, 2, 3)
val copy = original
copy.add(4)
println(original) // \[1, 2, 3, 4\] — потому что это одна и та же коллекция
Если нужно создать независимую копию, нужно использовать toList(), toMutableList():
val copy = original.toMutableList()
Поведение в многопоточности
Все эти коллекции не потокобезопасны. Если вы работаете с несколькими потоками, необходимо использовать синхронизацию вручную или специальные коллекции (CopyOnWriteArrayList, Collections.synchronizedList(...), ConcurrentLinkedQueue и т.д.).
Примеры с типами:
val a = listOf(1, 2, 3) // List<Int>
val b = mutableListOf(1, 2, 3) // MutableList<Int>
val c = arrayListOf(1, 2, 3) // ArrayList<Int>
fun printList(l: List<Int>) { ... }
fun modifyList(m: MutableList<Int>) { ... }
fun javaInterop(a: ArrayList<Int>) { ... }
Вызов modifyList(a) — ошибка, потому что a — List, не MutableList.
Типизация
Хотя ArrayList реализует MutableList, в некоторых случаях вам может потребоваться указать тип явно:
val list: MutableList<String> = arrayListOf() // OK
val arrayList: ArrayList<String> = mutableListOf() // Ошибка, т.к. mutableListOf возвращает MutableList, а не обязательно ArrayList
Это важно при использовании API, ожидающего конкретную реализацию.
Модификация через ссылки
fun change(list: MutableList<Int>) {
list\[0\] = 99
}
val nums = mutableListOf(1, 2, 3)
change(nums)
println(nums) // \[99, 2, 3\]
Функция change изменяет тот же список, потому что передаётся ссылка.
Пример для визуального понимания
val l1 = listOf(1, 2, 3) // нельзя изменить
val l2 = mutableListOf(1, 2, 3) // можно изменить
val l3 = arrayListOf(1, 2, 3) // можно изменить, и тип — ArrayList
// попытка l1.add(4) — ошибка
l2.add(4) // \[1, 2, 3, 4\]
l3.add(5) // \[1, 2, 3, 5\]
Эти различия становятся особенно важными при передаче коллекций в функции, взаимодействии с внешними библиотеками, или при необходимости строго контролировать изменения.