Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,249 @@
---
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)