Avatar ·

В чем разница между ArrayList<String>() и mutableListOf<String>() в Kotlin?

Привет! Я начал изучать Kotlin после Java и заметил, что для создания изменяемого списка можно использовать два способа: ```kotlin val list1: ArrayList = ArrayList() val list2: MutableList = mutableListOf() ``` Оба варианта вроде бы работают одинаково: я могу добавлять, удалять и изменять элементы. Но меня смущает, что в одном случае тип явно указан как `ArrayList`, а в другом — как `MutableList`. Я попробовал написать такой код: ```kotlin fun processList(list: MutableList) { list.add("Kotlin") } val arrayList = ArrayList() val mutableList = mutableListOf() processList(arrayList) // работает processList(mutableList) // тоже работает ``` Оба списка передаются в функцию, ожидающую `MutableList`. Значит, это одно и то же? Или есть какие-то тонкие различия в производительности, памяти или поведении? Может быть, `mutableListOf()` возвращает не `ArrayList`, а какую-то другую реализацию? Или это просто синтаксический сахар? Объясните, пожалуйста, в чем реальная разница и что лучше использовать в повседневной разработке. Я хочу понять не только "как", но и "почему". <<>>

Основное различие: тип vs реализация

Главное отличие лежит в плоскости типизации и абстракции. Давайте разберем оба подхода по порядку.

ArrayList<String>()

Здесь вы напрямую создаете экземпляр конкретного класса ArrayList, который является частью Java Collections Framework. Переменная объявлена с типом ArrayList<String>. Это означает:

  • Вы жестко привязываетесь к конкретной реализации списка.
  • Код будет использовать именно ArrayList с его внутренней структурой на основе массива.
  • Если в будущем вы захотите сменить реализацию (например, на LinkedList), придется менять объявление переменной.
// Жесткая привязка к ArrayList
val list: ArrayList<String> = ArrayList()
list.add("Java")
// list теперь может быть только ArrayList

mutableListOf<String>()

Это фабричная функция из стандартной библиотеки Kotlin. Она возвращает MutableList<T> — интерфейс, а не конкретный класс. Важные моменты:

  • Тип переменной — MutableList<String> (интерфейс), а не конкретная реализация.
  • Внутри mutableListOf() по умолчанию создается ArrayList, но это деталь реализации.
  • Вы не зависите от конкретной реализации — это программирование на уровне интерфейсов.
// Программирование через интерфейс
val list: MutableList<String> = mutableListOf()
list.add("Kotlin")
// Реализация может быть любой, соответствующей интерфейсу

Что же происходит под капотом?

Если заглянуть в исходники Kotlin, то mutableListOf() — это inline-функция, которая вызывает конструктор ArrayList. Но! Она возвращает результат, приведенный к типу MutableList. То есть:

public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

Оба варианта в рантайме создают один и тот же объект — java.util.ArrayList. Разница исключительно в типе переменной.

Практические рекомендации

В 95% случаев используйте mutableListOf(). Почему?

  • Гибкость. Вы можете легко заменить реализацию, не меняя код. Например, если понадобится потокобезопасный список или список на основе связного списка — достаточно изменить фабричный метод.
  • Единообразие. В Kotlin принято работать через интерфейсы. Для неизменяемых списков используйте listOf(), для изменяемых — mutableListOf().
  • Читаемость. Другие разработчики сразу поймут, что список изменяемый, и не будут путаться с Java-стилем.
// Хороший стиль Kotlin
val numbers: MutableList<Int> = mutableListOf(1, 2, 3)
numbers.add(4)

// Плохой стиль (Java-наследие)
val numbers2: ArrayList<Int> = ArrayList()
numbers2.add(4)

Когда все же использовать ArrayList?

Есть редкие случаи, когда это оправдано:

  • Вы работаете с Java-библиотекой, которая требует именно ArrayList.
  • Вам нужны специфические методы ArrayList, которых нет в интерфейсе (например, trimToSize() или ensureCapacity()).
  • Вы пишете критичный к производительности код и хотите явно указать реализацию для избежания лишних виртуальных вызовов (хотя современные JIT-компиляторы это оптимизируют).

Итог

ArrayList<String>() — это конкретный класс. mutableListOf<String>() — это фабричный метод, возвращающий интерфейс. По умолчанию оба создают ArrayList, но второй вариант предпочтительнее, так как следует принципам ООП и Kotlin-стилю.

<<>>

Дополнение: производительность и скрытые возможности

Коллега верно описал основное различие. Я добавлю несколько технических нюансов, которые стоит знать.

Производительность

Многие думают, что mutableListOf() медленнее из-за вызова фабричной функции, но это не так. Благодаря inline-механизму Kotlin, код mutableListOf() встраивается прямо в место вызова, и никакого оверхеда нет. Оба варианта компилируются в одинаковый байт-код.

// Код на Kotlin
val a = mutableListOf<String>()
val b = ArrayList<String>()

// Оба превращаются в:
// new ArrayList()

Что еще возвращает mutableListOf?

Хотя по умолчанию это ArrayList, в некоторых средах (например, Kotlin/Native или Kotlin/JS) реализация может отличаться. mutableListOf() гарантирует только соблюдение контракта MutableList, но не конкретную реализацию. Это особенно важно для мультиплатформенных проектов.

Практический совет

Если вам нужно создать список с начальными элементами, mutableListOf удобнее:

// mutableListOf с элементами
val list = mutableListOf("A", "B", "C")

// ArrayList так не умеет, нужно писать длиннее
val list2 = ArrayList<String>(listOf("A", "B", "C"))

В целом, придерживайтесь правила: в Kotlin используйте Kotlin-стиль. mutableListOf() — это идиоматично, читаемо и гибко. ArrayList() оставьте для обратной совместимости с Java-кодом.