The classic book, Programming in Scala, states that you need to be careful about using var
fields when defining equals
methods. They specifically state:
Pitfall #3: Defining equals in terms of mutable fields.
Personally, I think I've always defined equals
methods with mutable fields, at least in Java. They share the following source code example that demonstrates a problem when defining an equals
method in a Scala class when used in a collection.
# create the class scala> class Point(var x: Int, var y: Int) { | override def hashCode = 41 * (41 + x) + y | override def equals(other: Any) = other match { | case that: Point => this.x == that.x && this.y == that.y | case _ => false | } | } defined class Point # create an instance scala> val p = new Point(1, 2) p: Point = Point@6bc # create a collection with the point instance scala> val coll = scala.collection.immutable.HashSet(p) coll: scala.collection.immutable.HashSet[Point] = Set(Point@6bc) # the collection contains the point (this is good, and normal) scala> coll contains p res8: Boolean = true # change a property of the instance scala> p.x += 1 # BAM! the collection no longer appears to contain the instance scala> coll contains p res10: Boolean = false # huh, the iterator can see it scala> coll.iterator contains p res11: Boolean = true
I don't have time to test this with Java right now, but this was a huge surprise to me when I first learned about it. As mentioned, I always used mutable fields in Java. Take the case of a Person
class and a person's name; if their last name changes, it needs to be changed, and if that person
instance is in a collection, I always assumed that instance could still be found. (As a practical matter I don't remember ever needing to do this with a HashSet or HashMap.)
Regarding Scala, the book states, "after the change to the x
field, the point p
ended up in the wrong hash bucket of the set coll
. That is, its original hash bucket no longer corresponded to the new value of its hash code."