Avatar ·

IntArray vs Array<Int> в Kotlin: в чем разница и что выбрать?

Привет, коллеги! Я начал изучать Kotlin и столкнулся с непониманием: в языке есть два типа для работы с массивами целых чисел — IntArray и Array. На первый взгляд они похожи, но компилятор ругается, когда я пытаюсь их смешивать. Вот пример, который меня смутил: ```kotlin val arr1: IntArray = intArrayOf(1, 2, 3) val arr2: Array = arrayOf(1, 2, 3) // Пытаюсь присвоить один другому — ошибка! // arr1 = arr2 // Type mismatch ``` Почему так? Ведь оба хранят целые числа. Я читал, что IntArray — это примитивный тип, а Array — ссылочный, но не понимаю, как это влияет на производительность и когда что использовать. Также заметил, что у них разные методы. Например, у IntArray есть sum() и average() прямо из коробки, а у Array нужно писать .map { it.toDouble() }. Почему так? И еще вопрос: если я пишу функцию, которая принимает массив чисел, какой тип выбрать, чтобы она работала и с IntArray, и с Array? Пробовал использовать List — он работает, но это же не массив. Подскажите, пожалуйста, в чем фундаментальная разница и как правильно выбирать между ними в реальных проектах. Заранее спасибо! <<>>

Основное различие: примитивы vs объекты

В Kotlin IntArray — это массив примитивных типов int (как в Java), а Array<Int> — массив ссылочных объектов Integer. Это ключевое отличие, которое влияет на память, производительность и поведение.

1. Производительность и память

  • IntArray хранит значения непосредственно в памяти (как int[] в Java). Занимает 4 байта на элемент, без упаковки (boxing) и распаковки (unboxing).
  • Array<Int> хранит ссылки на объекты Integer. Каждый элемент — это объект на куче (heap), что занимает 16-24 байта + накладные расходы на упаковку примитива.

Пример сравнения:

val millionInts = IntArray(1_000_000) // ~4 MB
val millionIntegers = Array(1_000_000) { 0 } // ~16-24 MB + overhead

Для больших массивов IntArray выигрывает в 4-6 раз по памяти и в 2-3 раза по скорости доступа.

2. Nullable и дженерики

  • IntArray не может содержать null — это массив ненулевых примитивов.
  • Array<Int?> может содержать null, так как это массив ссылок.
  • Array<Int> (без ?) также не может содержать null, но при присваивании происходит упаковка.

Пример:

val ints = IntArray(3) // [0, 0, 0]
// ints[0] = null // Ошибка компиляции!

val objects = arrayOfNulls<Int>(3) // [null, null, null]
objects[0] = 42 // Упаковка int в Integer

3. Доступные методы

IntArray — это специализированный класс, который наследуется от Collection? Нет, он наследуется от Any, но имеет встроенные методы для работы с числами:

val arr = intArrayOf(1, 2, 3, 4, 5)
println(arr.sum())      // 15
println(arr.average())  // 3.0
println(arr.min())      // 1
println(arr.max())      // 5

Array<Int> таких методов не имеет — нужно использовать экстеншены:

val arr = arrayOf(1, 2, 3, 4, 5)
println(arr.sum()) // Работает через экстеншен, но медленнее из-за упаковки

4. Взаимодействие с Java

  • IntArray компилируется в int[] — идеально для Java-библиотек.
  • Array<Int> компилируется в Integer[] — может потребоваться преобразование.

Когда что выбирать?

  • IntArray: для больших массивов чисел, где важна производительность и память (научные расчеты, графика, обработка сигналов).
  • Array<Int>: когда нужны nullable элементы, работа с дженериками (например, List<Array<Int>>) или совместимость с Java-кодом, ожидающим Integer[].

Как сделать универсальную функцию?

Если нужно работать и с IntArray, и с Array<Int>, используйте List<Int> или дженерики:

fun processNumbers(numbers: Collection<Int>) {
    println("Сумма: ${numbers.sum()}")
}

// Работает с обоими:
processNumbers(intArrayOf(1,2,3).toList())
processNumbers(arrayOf(1,2,3).toList())

Или сделайте перегрузку функций для обоих типов.

<<>>

Дополнение: преобразование между типами

Если вы уже используете один тип, но нужно передать данные в функцию, ожидающую другой, используйте встроенные методы:

// IntArray -> Array<Int>
val intArray = intArrayOf(1, 2, 3)
val arrayFromInts: Array<Int> = intArray.toTypedArray()

// Array<Int> -> IntArray
val array = arrayOf(1, 2, 3)
val intArrayFromArray: IntArray = array.toIntArray()

Особенности копирования

При преобразовании создается новый массив — копирование данных. Для больших массивов это может быть дорого. Старайтесь сразу выбирать правильный тип.

Когда IntArray может быть неудобен?

  • Если вы используете функции высшего порядка (например, map, filter), результат часто возвращается как List, а не IntArray.
  • Для работы с API, ожидающими List<Int>, проще использовать Array<Int> или List<Int>.

Совет для новичков

Начинайте с List<Int> — это универсальный тип, который часто используется в Kotlin. Переходите на IntArray только когда профилирование покажет проблемы с производительностью. В 90% случаев List или Array достаточно.