In this quick article, we’re going to see the difference between IntArray and Array<Int> in Kotlin. Obviously, we can generalize the difference to other specialized primitive arrays as well.
For the impatient, Kotlin compiles the Array<Int> to Integer and the IntArray to int under the hood. We should prefer using the latter because of its more efficient representation unless we have a good reason not to.
Now, let’s dive into details!
2. Different Array Representations
When targeting the JVM, the Kotlin compiler will represent its arrays as JVM arrays. At the language level, Kotlin provides two sets of arrays:
- The Array<T> type, in which the type parameter can be any Kotlin type, such as Array<String> or Array<Int>
- Specialized primitive arrays, such as IntArray
We have these two forms mainly because the JVM can create and manipulate arrays in two ways:
- The newarray opcode creates an array of primitive types (int, for example) and manipulates it with *astore (iastore for an array of int) and *aload opcodes
- The anewarray opcode creates an array of reference types and manipulates it with aastore and aaload opcodes
The specialized primitive opcodes allow the JVM to optimize the creation and manipulation of primitive arrays.
Now that we know a little more about theory, let’s compare the bytecode representations of these two types of arrays.
3. The Array<Int> Bytecode Representation
In order to see the difference in action, let’s consider a simple snippet:
val arrayOfInts = arrayOf<Int>(42)
Here, the inferred type is, of course, Array<Int>. To see the generated bytecode, first, we should compile the Kotlin code:
>> kotlinc Arrays.kt
Now, we can use the javap tool to check out the generated bytecode. Anyway, this is how JVM creates the array:
>> javap -c -p ArraysKt 0: iconst_1 1: anewarray #8 // class java/lang/Integer
So, the Array<Int> in Kotlin has translated to an array of java.lang.Integer (Integer). The anewarray instruction creates an array of size 1 (The iconst_1 bit). This is a rather inefficient representation of arrays in Kotlin, and we should only use them when we need to store null values.
In order to set the first array element to 42, the JVM does a conversion:
6: iconst_0 7: bipush 42 9: invokestatic #12 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 12: aastore
The JVM converts the literal 42 to an Integer instance by calling the Integer.valueOf(int) static method. So, every time we add something to an Array<Int>, unwanted boxing of primitive values happens under the hood, another inefficient bit!
4. The IntArray Bytecode Representation
Let’s create an IntArray in Kotlin and initialize it with one element:
val intArray = intArrayOf(42)
After compilation, we can see that the JVM creates this array like:
15: iconst_1 16: newarray int
As shown above, the JVM uses the specialized newarray instruction to create an array of primitive int values. Therefore, the IntArray has translated to an int at the bytecode level.
As we might expect, loading and storing into such arrays are implemented without any unnecessary boxing or unboxing:
20: iconst_0 21: bipush 42 23: iastore
Here, the JVM stores the literal 42 as-is into the array as the first element without any boxing!
In this article, we learned how primitive arrays and their corresponding Array<T> types are different in terms of JVM representation and API. The bottom line is, we should prefer using the IntArray and other specialized primitive arrays unless we need to store null values alongside those primitives.