12 KiB
title, description, tags
| title | description | tags | ||
|---|---|---|---|---|
| Массивы | Array, специализированные IntArray, сравнение через contentEquals и почему обычно нужен List |
|
Массив - фиксированная по размеру последовательность элементов. В Kotlin это класс Array<T>, а не синтаксическая конструкция.
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.
Специализированные массивы
Для чисел есть отдельные классы без упаковки:
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 - примитивы. В горячем коде это заметно по памяти и скорости.
val boxed: Array<Int> = arrayOf(1, 2, 3) // Integer[] — аллокация на элемент
val raw: IntArray = intArrayOf(1, 2, 3) // int[] — без упаковки
println("${boxed.size} ${raw.size}")
Сравнение - только contentEquals
== на массивах сравнивает ссылки, а не содержимое. Это главная ловушка:
val a = arrayOf(1, 2, 3)
val b = arrayOf(1, 2, 3)
println(a == b) // false — разные объекты!
println(a.contentEquals(b)) // true — так правильно
println(a.contentHashCode()) // хеш по содержимому
Для вложенных массивов - глубокие версии:
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() массива бесполезен:
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().
Обход
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
Операции
Массивы поддерживают те же операции, что коллекции:
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.
Копирование и изменение размера
Размер массива неизменен, поэтому «добавление» - это создание нового:
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]
Преобразование в коллекции и обратно
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}")
Многомерные массивы
Отдельного типа нет - это массив массивов:
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]
Строки могут иметь разную длину (рваный массив):
val jagged = arrayOf(intArrayOf(1), intArrayOf(1, 2, 3))
println(jagged.joinToString { it.size.toString() }) // 1, 3
Когда массив, а когда List
Массив нужен редко. Используйте его, если:
- важна производительность и нужны примитивы без упаковки (
IntArray) - работаете с API, требующим массив (
vararg, Java-библиотеки) - размер фиксирован и известен заранее
В остальных случаях - List: он не даёт менять себя случайно, имеет больше операций и корректный equals.
val list = listOf(1, 2, 3)
println(list == listOf(1, 2, 3)) // true — работает из коробки
Подробнее: Коллекции
vararg
Параметр с переменным числом аргументов внутри функции - массив:
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-оператор
}
Подробнее: Функции
[!info] Отличия от Java
Java Kotlin int[] a = {1,2,3}val a = intArrayOf(1,2,3)new int[5]IntArray(5)a.lengtha.sizeArrays.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)для varargsf(*arr)— нужен spreadСтатические методы
Arraysстали методами массива - находятся автодополнением, а не поиском по документации.Разделение
IntArray/Array<Int>сделано намеренно: в JavaInteger[]иint[]тоже разные, но это легко не заметить. В Kotlin разные имена не позволяют спутать упакованный массив с примитивным.Массивы в Kotlin инвариантны, в Java - ковариантны. Java разрешает
Object[] o = new String[1]; o[0] = 1;и падает в рантайме (ArrayStoreException). В KotlinArray<Any> = Array<String>не скомпилируется. Подробнее: ВариантностьСравнить: Массивы в Java
[!warning] Подводные камни
==на массивах сравнивает ссылки. Самая частая ошибка:val a = arrayOf(1, 2) println(a == arrayOf(1, 2)) // false println(a.contentEquals(arrayOf(1, 2))) // trueПо этой же причине массив нельзя использовать как ключ
Mapили вSet-hashCodeсчитается по ссылке.Массив внутри
data classломаетequals: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
println(array)печатает мусор вида[Ljava.lang.Integer;@1b6d3586. То же в строковых шаблонах.
valне защищает элементы:val a = intArrayOf(1, 2) a[0] = 99 // разрешено println(a.contentToString()) // [99, 2]Неизменяемость даёт
List, а неval.
Array<Int>вместоIntArray- скрытая просадка. Каждый элемент упакован вInteger: больше памяти и промахи кеша.
arrayOfNullsиArray(n)для непримитивных типов требуют разного подхода:val a = arrayOfNulls<String>(3) // Array<String?> val b = Array(3) { "" } // Array<String> — инициализатор обязателен println("${a.size} ${b.size}")Создать
Array<String>без инициализатора нельзя - компилятор не позволит оставить non-null элементы пустыми.
Дополнительно: Коллекции Далее: Функции