I have a simple data class which stores x and y coordinates of a position. My use case is that a single object of this class will be created and updated, and I need to maintain a set of unique coordinates.
I’ve simplified my use case in the following code where adding the pos object directly to the set vs passing the copy of the object result in different behavior (please see the comment in the code).
My initial hunch was that it could be because Java/Kotlin is passing the object by reference and the Set.add compares on reference. However, that doesn’t seem to be true, if I set the pos.x or pos.y to any other value then the set.contains method returns false.
Question:
If the comparison is by reference then why does it fail when setting to a value other than what is given in the below code? If comparison is by hash code then why does the setByCopy not return true in the original case?
data class Pos(var x: Int = 0, var y: Int = 0)
fun main() {
val pos = Pos(0, 0)
val set = mutableSetOf<Pos>()
val setByCopy = mutableSetOf<Pos>()
pos.x = -9
pos.y = -6
set.add(pos)
setByCopy.add(pos.copy())
println(pos.hashCode())
pos.x = -8
pos.y = -37
// setting pos.y to any other value (e.g -35) will cause set.contains(pos) to return false.
println(set.contains(pos)) // true, but expected false.
println(setByCopy.contains(pos)) // false
}
>Solution :
As usual, modifying an element that’s already in a set produces undefined behavior. This is not explicitly documented in Kotlin, but carries over from Java, where it’s documented:
Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
This means that anything can happen: it can work or not work randomly.