--- title: Массивы description: Array, специализированные IntArray, сравнение через contentEquals и почему обычно нужен List tags: - kotlin - stdlib --- Массив - фиксированная по размеру последовательность элементов. В Kotlin это класс `Array`, а не синтаксическая конструкция. ```kotlin val a = arrayOf(1, 2, 3) val b = Array(3) { it * 2 } // [0, 2, 4] — по лямбде от индекса val c = arrayOfNulls(3) // [null, null, null] val d = emptyArray() println(a[0]) // 1 a[0] = 10 println(a.size) // 3 println(a.joinToString()) // 10, 2, 3 ``` Размер задаётся при создании и не меняется. Элементы изменяемы всегда - даже если массив объявлен через `val`. ## Специализированные массивы Для чисел есть отдельные классы без упаковки: ```kotlin val ints = intArrayOf(1, 2, 3) val zeros = IntArray(5) // [0, 0, 0, 0, 0] — заполнен нулями val squares = IntArray(5) { it * it } // [0, 1, 4, 9, 16] val doubles = doubleArrayOf(1.0, 2.0) val chars = charArrayOf('a', 'b') val bools = BooleanArray(3) // [false, false, false] ``` | Класс | Байт-код | Аналог в Java | |---|---|---| | `IntArray` | `int[]` | `int[]` | | `LongArray` | `long[]` | `long[]` | | `DoubleArray` | `double[]` | `double[]` | | `CharArray` | `char[]` | `char[]` | | `BooleanArray` | `boolean[]` | `boolean[]` | | `Array` | `Integer[]` | `Integer[]` | Разница принципиальна: `Array` хранит **упакованные** объекты, `IntArray` - примитивы. В горячем коде это заметно по памяти и скорости. ```kotlin val boxed: Array = arrayOf(1, 2, 3) // Integer[] — аллокация на элемент val raw: IntArray = intArrayOf(1, 2, 3) // int[] — без упаковки println("${boxed.size} ${raw.size}") ``` ## Сравнение - только contentEquals `==` на массивах сравнивает **ссылки**, а не содержимое. Это главная ловушка: ```kotlin val a = arrayOf(1, 2, 3) val b = arrayOf(1, 2, 3) println(a == b) // false — разные объекты! println(a.contentEquals(b)) // true — так правильно println(a.contentHashCode()) // хеш по содержимому ``` Для вложенных массивов - глубокие версии: ```kotlin val x = arrayOf(arrayOf(1), arrayOf(2)) val y = arrayOf(arrayOf(1), arrayOf(2)) println(x.contentEquals(y)) // false — сравнивает ссылки вложенных println(x.contentDeepEquals(y)) // true ``` ## Печать `toString()` массива бесполезен: ```kotlin val a = arrayOf(1, 2, 3) println(a) // [Ljava.lang.Integer;@1b6d3586 println(a.contentToString()) // [1, 2, 3] println(a.joinToString()) // 1, 2, 3 println("$a".startsWith("[L")) // true — шаблон вызывает тот же toString ``` Внутри строкового шаблона всегда нужен `contentToString()` или `joinToString()`. ## Обход ```kotlin val a = arrayOf("x", "y", "z") for (item in a) print(item) // xyz for (i in a.indices) print(a[i]) // xyz for ((i, v) in a.withIndex()) print("$i$v ") // 0x 1y 2z a.forEach { print(it) } // xyz a.forEachIndexed { i, v -> print("$i$v ") } println() println(a.lastIndex) // 2 ``` ## Операции Массивы поддерживают те же операции, что коллекции: ```kotlin val a = intArrayOf(3, 1, 2) println(a.sorted()) // [1, 2, 3] — возвращает List! println(a.sortedArray()) // новый IntArray a.sort() // сортировка на месте println(a.contentToString()) // [1, 2, 3] println(a.sum()) // 6 println(a.average()) // 2.0 println(a.max()) // 3 println(a.filter { it > 1 }) // [2, 3] — List println(a.map { it * 2 }) // [2, 4, 6] — List println(a.reversed()) // [3, 2, 1] — List println(a.reversedArray().contentToString()) // [3, 2, 1] println(a.contains(2)) // true println(2 in a) // true println(a.indexOf(2)) // 1 ``` Обратите внимание: `filter`, `map`, `sorted`, `reversed` возвращают `List`, а не массив. Версии, сохраняющие тип, имеют суффикс `Array`. ## Копирование и изменение размера Размер массива неизменен, поэтому «добавление» - это создание нового: ```kotlin val a = intArrayOf(1, 2, 3) println((a + 4).contentToString()) // [1, 2, 3, 4] — новый массив println(a.copyOf().contentToString()) // [1, 2, 3] println(a.copyOf(5).contentToString()) // [1, 2, 3, 0, 0] — дополнен нулями println(a.copyOf(2).contentToString()) // [1, 2] — обрезан println(a.copyOfRange(1, 3).contentToString()) // [2, 3] println(a.sliceArray(0..1).contentToString()) // [1, 2] a.fill(0) println(a.contentToString()) // [0, 0, 0] ``` ## Преобразование в коллекции и обратно ```kotlin val a = arrayOf(1, 2, 3) val list: List = a.toList() val set = a.toSet() val back: Array = list.toTypedArray() val raw: IntArray = list.toIntArray() println("${list} ${set} ${back.size} ${raw.size}") ``` ## Многомерные массивы Отдельного типа нет - это массив массивов: ```kotlin val grid = Array(3) { IntArray(4) } // 3x4, заполнен нулями grid[1][2] = 5 println(grid[1].contentToString()) // [0, 0, 5, 0] val table = Array(2) { r -> IntArray(2) { c -> r * 2 + c } } println(table.joinToString { it.contentToString() }) // [0, 1], [2, 3] ``` Строки могут иметь разную длину (рваный массив): ```kotlin val jagged = arrayOf(intArrayOf(1), intArrayOf(1, 2, 3)) println(jagged.joinToString { it.size.toString() }) // 1, 3 ``` ## Когда массив, а когда List Массив нужен редко. Используйте его, если: - важна производительность и нужны примитивы без упаковки (`IntArray`) - работаете с API, требующим массив (`vararg`, Java-библиотеки) - размер фиксирован и известен заранее В остальных случаях - `List`: он не даёт менять себя случайно, имеет больше операций и корректный `equals`. ```kotlin val list = listOf(1, 2, 3) println(list == listOf(1, 2, 3)) // true — работает из коробки ``` Подробнее: [Коллекции](../../Коллекции/Коллекции.md) ## vararg Параметр с переменным числом аргументов внутри функции - массив: ```kotlin fun sum(vararg nums: Int): Int { var total = 0 for (n in nums) total += n // nums это IntArray return total } fun main() { println(sum(1, 2, 3)) // 6 println(sum()) // 0 val arr = intArrayOf(1, 2) println(sum(*arr)) // 3 — spread-оператор } ``` Подробнее: [Функции](Функции.md) > [!info] Отличия от Java > | Java | Kotlin | > |---|---| > | `int[] a = {1,2,3}` | `val a = intArrayOf(1,2,3)` | > | `new int[5]` | `IntArray(5)` | > | `a.length` | `a.size` | > | `Arrays.toString(a)` | `a.contentToString()` | > | `Arrays.equals(a,b)` | `a.contentEquals(b)` | > | `Arrays.deepEquals(a,b)` | `a.contentDeepEquals(b)` | > | `Arrays.sort(a)` | `a.sort()` | > | `Arrays.copyOf(a,n)` | `a.copyOf(n)` | > | `Arrays.fill(a,0)` | `a.fill(0)` | > | `int[]` и `Integer[]` | `IntArray` и `Array` | > | `f(arr)` для varargs | `f(*arr)` — нужен spread | > > Статические методы `Arrays` стали методами массива - находятся автодополнением, а не поиском по документации. > > Разделение `IntArray` / `Array` сделано намеренно: в Java `Integer[]` и `int[]` тоже разные, но это легко не заметить. В Kotlin разные имена не позволяют спутать упакованный массив с примитивным. > > **Массивы в Kotlin инвариантны**, в Java - ковариантны. Java разрешает `Object[] o = new String[1]; o[0] = 1;` и падает в рантайме (`ArrayStoreException`). В Kotlin `Array = Array` не скомпилируется. Подробнее: [Вариантность](../../Дженерики/Вариантность.md) > > Сравнить: [Массивы в Java](../../../Программирование%20на%20Java/Синтаксис%20и%20типы/Работа%20с%20данными%20и%20типами/Массивы.md) > [!warning] Подводные камни > **`==` на массивах сравнивает ссылки.** Самая частая ошибка: > ```kotlin > val a = arrayOf(1, 2) > println(a == arrayOf(1, 2)) // false > println(a.contentEquals(arrayOf(1, 2))) // true > ``` > По этой же причине массив нельзя использовать как ключ `Map` или в `Set` - `hashCode` считается по ссылке. > > **Массив внутри `data class` ломает `equals`:** > ```kotlin > data class Packet(val data: ByteArray) > > fun main() { > val p1 = Packet(byteArrayOf(1)) > println(p1 == Packet(byteArrayOf(1))) // false! > } > ``` > Сгенерированный `equals` сравнивает массив по ссылке, а не по содержимому. Поэтому в `data class` с массивом `equals`/`hashCode` пишут руками. Подробнее: [Data class](../Базовые%20типы%20ООП/Data%20class.md) > > **`println(array)` печатает мусор** вида `[Ljava.lang.Integer;@1b6d3586`. То же в строковых шаблонах. > > **`val` не защищает элементы:** > ```kotlin > val a = intArrayOf(1, 2) > a[0] = 99 // разрешено > println(a.contentToString()) // [99, 2] > ``` > Неизменяемость даёт `List`, а не `val`. > > **`Array` вместо `IntArray` - скрытая просадка.** Каждый элемент упакован в `Integer`: больше памяти и промахи кеша. > > **`arrayOfNulls` и `Array(n)` для непримитивных типов требуют разного подхода:** > ```kotlin > val a = arrayOfNulls(3) // Array > val b = Array(3) { "" } // Array — инициализатор обязателен > println("${a.size} ${b.size}") > ``` > Создать `Array` без инициализатора нельзя - компилятор не позволит оставить non-null элементы пустыми. Дополнительно: [Коллекции](../../Коллекции/Коллекции.md) Далее: [Функции](Функции.md)