A quick question regarding `data class` and `set.c...
# getting-started
k
A quick question regarding
data class
and
set.contains(e)
If I didn't understand it wrong,
set.contains(e)
checks
elementInSet.equals(e)
. Also, data class creates
equals()
method for the properties in the primary constructor. Now, I'm wondering why I get this output:
Copy code
data class Foo(var v: Int)

fun test() {
    val mySet = mutableSetOf(Foo(1))
    mySet.forEach {
        it.v += 1
    }
    println("mySet contains: $mySet")
    val f = Foo(2)
    println("$f in Set? ${f in mySet}")
    println("equals? ${f == mySet.toList().first()}")
}
Output:
Copy code
mySet contains: [Foo(v=2)]
Foo(v=2) in Set? false
equals? true
Why
f in mySet
returns false?
m
mutableSetOf
creates a linked hash set and it not suitable for storing mutable objects. I'm not aware of any efficient set that can handle mutable objects. When you change
v
you change the
hashcode
of the
Foo
object and the set can no longer find it.
☝️ 1
k
I also thought about the hashcode diff. However, I checked the Java Set.contains, it says
boolean contains(Object o)
Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e)).
m
It does use
equals
, but it uses
hashcode
to reduce the number of items to consider for
equals
. So it will only use
equals
when the
hashcode
puts them in the same bucket.
c
The
hashCode
contract ensures that using the hash code to make fast equality checks is always legal, so it doesn't have to be documented everywhere It's not legal in Java or Kotlin to add an object whose identity changes over time as a key (value in a set, key in a map…)
k
Does it mean Kotlin's Set.contains() may behave different from Java's
Set.contains()
?
c
Set
is an interface,
HashSet
and
LinkedHashSet
don't behave the same way.
m
No Java's
HashSet
behaves the same way, and
TreeSet
actual breaks that contract.
c
"returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e)).
Does not mean that the function calls only equals. It means that, for a well implemented object, it behaves the same way as if it did.
👍 1
All
Set
implementations in Java and Kotlin do respect that contract
k
Thanks guys. I think I misunderstood the javaDoc, I thought it calls equals() literally. So the lesson I learned is to avoid using Set to store mutable elements.
c
All implementations are allowed to make shortcuts in their actual code, as long as the result is the same (and that's what you're seeing here: you broke the "storing mutable elements", and it was relying on that rule to make the equals check faster)
m
Or don't modify them after storing them.
Date
is a mutable object, but I would still store them in
Sets
because I know that I would not modify them and I pretended they were immutable.
👍 1
From the
Set
class documentation.
Note: 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. A special case of this prohibition is that it is not permissible for a set to contain itself as an element.
👍 1
k
Yep, it took me half day to finally spot the cause in my real project. Really hard to find ... 💢
c
One of the reasons many people advocate for using only immutable objects 🙂