Files
obsidian/Kotlin/Синтаксис и типы/Работа с данными/Массивы.md
T

250 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Массивы
description: Array, специализированные IntArray, сравнение через contentEquals и почему обычно нужен List
tags:
- kotlin
- stdlib
---
Массив - фиксированная по размеру последовательность элементов. В Kotlin это класс `Array<T>`, а не синтаксическая конструкция.
```kotlin
val a = arrayOf(1, 2, 3)
val b = Array(3) { it * 2 } // [0, 2, 4] — по лямбде от индекса
val c = arrayOfNulls<String>(3) // [null, null, null]
val d = emptyArray<Int>()
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<Int>` | `Integer[]` | `Integer[]` |
Разница принципиальна: `Array<Int>` хранит **упакованные** объекты, `IntArray` - примитивы. В горячем коде это заметно по памяти и скорости.
```kotlin
val boxed: Array<Int> = 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<Int> = a.toList()
val set = a.toSet()
val back: Array<Int> = 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<Int>` |
> | `f(arr)` для varargs | `f(*arr)` — нужен spread |
>
> Статические методы `Arrays` стали методами массива - находятся автодополнением, а не поиском по документации.
>
> Разделение `IntArray` / `Array<Int>` сделано намеренно: в Java `Integer[]` и `int[]` тоже разные, но это легко не заметить. В Kotlin разные имена не позволяют спутать упакованный массив с примитивным.
>
> **Массивы в Kotlin инвариантны**, в Java - ковариантны. Java разрешает `Object[] o = new String[1]; o[0] = 1;` и падает в рантайме (`ArrayStoreException`). В Kotlin `Array<Any> = Array<String>` не скомпилируется. Подробнее: [Вариантность](../../Дженерики/Вариантность.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<Int>` вместо `IntArray` - скрытая просадка.** Каждый элемент упакован в `Integer`: больше памяти и промахи кеша.
>
> **`arrayOfNulls` и `Array(n)` для непримитивных типов требуют разного подхода:**
> ```kotlin
> val a = arrayOfNulls<String>(3) // Array<String?>
> val b = Array(3) { "" } // Array<String> — инициализатор обязателен
> println("${a.size} ${b.size}")
> ```
> Создать `Array<String>` без инициализатора нельзя - компилятор не позволит оставить non-null элементы пустыми.
Дополнительно: [Коллекции](../../Коллекции/Коллекции.md)
Далее: [Функции](Функции.md)