Kotlin: List vs Array — в чём подвох?
Если вы пишете на Kotlin хотя бы неделю, вы наверняка сталкивались с вопросом: «А почему я не могу просто использовать Array везде? Зачем мне эти List и MutableList?»
Этот вопрос — один из самых популярных на Stack Overflow. И не зря. На первый взгляд, и список, и массив хранят элементы, к ним можно обращаться по индексу, их можно перебирать. Но копните глубже — и вы обнаружите фундаментальные различия, которые влияют на производительность, безопасность кода и даже на то, как вы мыслите, проектируя архитектуру.
В этом туториале мы разложим по полочкам разницу между List и Array в Kotlin. Вы узнаете, когда использовать массив, а когда — список, и почему опытные разработчики почти всегда выбирают List.
1. База: что такое Array в Kotlin?
Array в Kotlin — это прямая проекция Java-массива. Это фиксированная последовательность элементов одного типа. Когда вы создаёте массив, вы сразу выделяете под него память. Изменить размер массива после создания нельзя.
// Создание массива
val numbers: Array<Int> = arrayOf(1, 2, 3, 4, 5)
println(numbers[0]) // 1
numbers[2] = 10 // можно менять значение
println(numbers[2]) // 10
Массивы в Kotlin — это mutable (изменяемые) по своей природе: вы всегда можете заменить элемент по индексу. Но вы не можете добавить новый элемент или удалить существующий. Размер зафиксирован навсегда.
Особенности Array:
- Фиксированный размер — нельзя добавить или удалить элемент.
- Изменяемое содержимое — можно перезаписывать элементы.
- Производительность — максимальная, так как это прямой доступ к памяти.
- Примитивные массивы — есть специальные типы:
IntArray,ByteArray,FloatArrayи т.д. Они работают без boxing-упаковки, что даёт прирост скорости.
// Примитивный массив — без boxing
val intArray: IntArray = intArrayOf(1, 2, 3, 4, 5)
2. База: что такое List в Kotlin?
List — это интерфейс из стандартной библиотеки Kotlin. В отличие от массива, список — это абстракция. Вы не знаете, как именно он хранит данные внутри (это может быть ArrayList, LinkedList или что-то ещё).
// Создание неизменяемого списка
val fruits: List<String> = listOf("apple", "banana", "cherry")
println(fruits[0]) // apple
// fruits[0] = "orange" // Ошибка компиляции!
Главная «фишка» Kotlin — разделение на read-only (List) и mutable (MutableList).
List (read-only):
- Нельзя изменить содержимое (ни добавить, ни удалить, ни заменить).
- Можно только читать.
- Безопаснее для передачи данных между компонентами.
MutableList:
- Можно добавлять, удалять, заменять элементы.
- Динамический размер.
- На практике чаще всего используется
ArrayListпод капотом.
val mutableFruits: MutableList<String> = mutableListOf("apple", "banana")
mutableFruits.add("orange") // OK
mutableFruits.remove("banana") // OK
mutableFruits[0] = "pear" // OK
3. Ключевые отличия List vs Array
Давайте сведём всё в наглядную таблицу (в коде — в виде комментариев).
// 1. Изменяемость
val array = arrayOf(1, 2, 3)
array[0] = 99 // ✅ Работает
val list = listOf(1, 2, 3)
// list[0] = 99 // ❌ Ошибка компиляции
val mutableList = mutableListOf(1, 2, 3)
mutableList[0] = 99 // ✅ Работает
// 2. Размер
val fixedArray = arrayOf(1, 2, 3)
// fixedArray.add(4) // ❌ Такого метода нет
val dynamicList = mutableListOf(1, 2, 3)
dynamicList.add(4) // ✅ Размер увеличился
// 3. Тип элементов
val mixedArray: Array<Any> = arrayOf(1, "hello", 3.14) // ❌ Не рекомендуется
val mixedList: List<Any> = listOf(1, "hello", 3.14) // Тоже не рекомендуется
// 4. Производительность
// Array быстрее при чтении/записи по индексу
// List (ArrayList) чуть медленнее, но гибче
// 5. Примитивные типы
val primitiveArray = IntArray(5) // Без boxing — быстро
val objectArray = Array<Int>(5) { 0 } // С boxing — медленнее
val listOfInts = listOf(1, 2, 3) // Тоже с boxing
4. Когда использовать Array?
Честно говоря, в повседневном коде на Kotlin массивы используются редко. Но есть сценарии, где они незаменимы:
- Работа с JVM-библиотеками — если вы вызываете Java-метод, который принимает массив.
- Высокопроизводительные вычисления — обработка аудио, видео, научные расчёты. Тут
IntArrayилиFloatArrayдают серьёзный выигрыш в скорости. - Varargs — функция с переменным числом аргументов (
vararg) внутри работает как массив. - Аннотации — значения аннотаций должны быть массивами.
fun printAll(vararg items: String) {
// items — это Array<String> внутри
for (item in items) {
println(item)
}
}
printAll("a", "b", "c")
5. Когда использовать List?
В 90% случаев выбирайте List или MutableList. Вот почему:
- Гибкость — динамический размер, методы
filter,map,findи т.д. - Безопасность — неизменяемый
Listзащищает данные от случайного изменения. - Совместимость с Kotlin-функциями — все коллекции Kotlin заточены под
List. - Лучшая практика — в современном Kotlin принято использовать коллекции, а не массивы.
data class User(val name: String, val age: Int)
val users: List<User> = listOf(
User("Alice", 25),
User("Bob", 30)
)
// Филь