В чем разница между List и Array в Kotlin? Когда что использовать?
Ключевые отличия: List (неизменяемый/изменяемый) vs Array
Отличный вопрос! Это одна из самых частых путаниц у новичков в Kotlin. Давайте разложим всё по полочкам.
Главное различие — тип и семантика:
- Array — это низкоуровневая структура данных, представляющая собой фиксированную последовательность элементов одного типа в памяти. Это прямой аналог массивов в Java (
int[],String[]). - List — это интерфейс (часть Kotlin Collections Framework), который представляет собой упорядоченную коллекцию. Он может иметь разные реализации (например,
ArrayList,LinkedList).
Основные различия в деталях
1. Изменяемость (Mutability)
Массивы всегда изменяемы — вы можете поменять значение любого элемента по индексу. Однако вы не можете изменить размер массива после создания.
Интерфейс List по умолчанию неизменяемый (read-only) — у него нет методов add, remove или сеттера. Для изменяемой коллекции используйте MutableList:
val immutableList: List<String> = listOf("a", "b")
// immutableList[0] = "c" // Ошибка!
val mutableList: MutableList<String> = mutableListOf("a", "b")
mutableList[0] = "c" // Работает
mutableList.add("d") // Работает
2. Типизация и ковариантность
Это важный момент для дженериков. Array<T> инвариантен в Kotlin. Это означает, что Array<String> не является подтипом Array<Any>. Почему? Потому что массив можно изменять (записывать значения), и это может привести к ошибкам типов во время выполнения.
А вот List<T> ковариантен (объявлен как interface List<out T>). Поэтому List<String> является подтипом List<Any> — это безопасно, так как вы не можете записывать в него (нет методов для изменения):
val strArray: Array<String> = arrayOf("a")
// val anyArray: Array<Any> = strArray // Ошибка компиляции!
val strList: List<String> = listOf("a")
val anyList: List<Any> = strList // Работает!
3. Производительность и тип данных
Для примитивных типов (Int, Double, Boolean и т.д.) Kotlin предоставляет специальные классы массивов для избежания автоупаковки (boxing): IntArray, DoubleArray, BooleanArray и т.д. Они работают как int[] в Java — очень быстро и без лишних объектов.
val intArray = intArrayOf(1, 2, 3) // Эффективно, без Integer объектов
val intList = listOf(1, 2, 3) // Каждый Int упакован в Integer
List<Int> всегда использует объекты Integer, что может быть критично для больших массивов данных.
Когда что использовать?
- Используйте Array, если:
- Вам нужна фиксированная длина (например, для буфера или технической структуры).
- Критична производительность и вы работаете с примитивами (
IntArrayи т.д.). - Вы вызываете Java-код, который ожидает массив.
- Вы работаете с аннотациями или vararg (например,
fun foo(vararg args: String)).
- Используйте List (по умолчанию), если:
- Вам нужны гибкие коллекции с возможностью добавления/удаления элементов (используйте
MutableList). - Вы хотите использовать богатый API коллекций:
filter,map,sortedByи т.д. - Вам нужна ковариантность (например, передавать список подтипов как список родительского типа).
- Вы пишете современный Kotlin-код — в 90% случаев
Listявляется правильным выбором.
- Вам нужны гибкие коллекции с возможностью добавления/удаления элементов (используйте
Итог: Для большинства повседневных задач (список пользователей, товаров, задач) используйте List / MutableList. Массивы оставьте для низкоуровневых операций, работы с примитивами или совместимости с Java.
Дополнение: Специальные случаи и идиомы Kotlin
Хочу добавить несколько практических моментов, которые помогут вам избежать ошибок.
1. Преобразование между Array и List
Вы легко можете конвертировать одно в другое, если это необходимо:
val array = arrayOf(1, 2, 3)
val listFromArray: List<Int> = array.toList()
val list = listOf(4, 5, 6)
val arrayFromList: Array<Int> = list.toTypedArray()
2. Размер и проверка на пустоту
У массивов есть свойство size, у списков — size (не count(), хотя count() тоже работает как расширение). Для проверки на пустоту используйте isEmpty() — он есть у обоих типов.
3. Важный нюанс с vararg
Если функция принимает vararg, вы можете передать массив со специальным оператором * (spread operator), но список так передать нельзя без конвертации:
fun printAll(vararg items: String) { items.forEach { println(it) } }
val arr = arrayOf("A", "B")
printAll(*arr) // Работает
val lst = listOf("C", "D")
// printAll(*lst) // Ошибка!
printAll(*lst.toTypedArray()) // Так можно
4. Идиоматический Kotlin
В сообществе Kotlin принято использовать List везде, где это возможно. Если вы видите в чужом коде Array без веской причины (производительность, совместимость с Java), скорее всего, это запах кода. Начните с listOf() или mutableListOf() и переходите к массивам только когда точно знаете, зачем они вам нужны.
Ответов пока нет.
Войдите чтобы оставить ответ