What you didn’t know about arrays in Kotlin

Since Google officially announced Kotlin as a first-class language for Android in 2017, here at the Android team @ASOS, we were really excited to use it. Data classes are a great feature of Kotlin. They remove the need to manually implement lots of boilerplate code, such as equals(), hash(), toString().

When using data classes you would assume equals(), hash(), toString()will work as expected but this is not always the case.

Let’s take the following data class, which holds a String and anIntArray:

data class NumArray(val name: String, val values: IntArray)

Consider the Java equivalent of the above data class:

In the above example, the constructor, getter, equals()and hashCode() were automatically generated by the IntelliJ IDE. Most of the time these will work in the same way, however when comparing two instances, you would be amazed at the result:

When running the code, you will see the print result is falsefor the data class, but truefor the Java class. Based on this, you see the issue with the usage of arrays in data classes.

In Kotlin (from docs)

arrays are always compared using equals(), as all other objects

It compares the array’s references, not the content. In the Java class, Arrays.equals() is used to check the content, not the reference. Since we created two arrays here, they have different references. The problem can be traced back to 2015 here. The root of the problem actually comes from a bug in the JVM.

It’s a long-standing, well-known issue on the JVM: equals() works differently for arrays and collections. Collections are compared structurally, while arrays are not, equals() for them simply resorts to referential equality: this === other.

So, how can we solve this problem? Luckily, since Kotlin 1.1, the methods contentEquals and contentHashCode were introduced to compare two arrays for structural equality. Based on the example above, we can override the equals()and hashCode()methods as below:

This means you lose one of the key benefits of using data classes; that you don’t need to override anything to use it. Since collections are compared structurally, when you have a data class like below, you don’t need to override anything to get the data class benefits.

data class NumList(val name: String, val values: List<Int>))

In Kotlin, when you compare two arrays, either directly or in a data class, consider the above before making the decision. In Java world, lists are preferable to arrays, which is also suggested in Effective Java. In Kotlin, you may want to do the same.

I hope you found this helpful. Feel free to leave comments or let me know if you’d like to hear more.