Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user