В чём разница между 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 &lt;T&gt; mutableListOf(vararg elements: T): MutableList&lt;T&gt;

Пример:

val names = mutableListOf("Alice", "Bob")
names.add("Charlie")
names\[0\] = "Ann"

Тип: MutableList<String> (наследует от List, реализует методы изменения)

Под капотом: возвращает ArrayList<T> (по умолчанию).

arrayListOf(...)

Создаёт конкретный экземпляр ArrayList, которая является реализацией изменяемого списка из стандартной Java-коллекции. Этот список идентичен тому, что создаётся при mutableListOf(), но тип указывается явно.

Сигнатура:

fun &lt;T&gt; arrayListOf(vararg elements: T): ArrayList&lt;T&gt;

Пример:

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&lt;Int&gt;
val b = mutableListOf(1, 2, 3) // MutableList&lt;Int&gt;
val c = arrayListOf(1, 2, 3) // ArrayList&lt;Int&gt;
fun printList(l: List&lt;Int&gt;) { ... }
fun modifyList(m: MutableList&lt;Int&gt;) { ... }
fun javaInterop(a: ArrayList&lt;Int&gt;) { ... }

Вызов modifyList(a) — ошибка, потому что a — List, не MutableList.

Типизация

Хотя ArrayList реализует MutableList, в некоторых случаях вам может потребоваться указать тип явно:

val list: MutableList&lt;String&gt; = arrayListOf() // OK
val arrayList: ArrayList&lt;String&gt; = mutableListOf() // Ошибка, т.к. mutableListOf возвращает MutableList, а не обязательно ArrayList

Это важно при использовании API, ожидающего конкретную реализацию.

Модификация через ссылки

fun change(list: MutableList&lt;Int&gt;) {
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\]

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