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 false
for the data class, but true
for 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.