Reputation: 3151
I'm new to Scala, but have a decent Java background. My question is about overriding the equals method in Scala. The following example is from the Scala cookbook:
class Person (name: String, age: Int) {
def canEqual(a: Any) = a.isInstanceOf[Person]
override def equals(that: Any): Boolean =
that match {
case that: Person => that.canEqual(this) && this.hashCode == that.hashCode
case _ => false
}
}
My question is why we need
that.canEqual(this)
My understanding is that that code will only be executed if 'that' is a person. So, why the extra call to isInstanceOf?
Upvotes: 2
Views: 959
Reputation: 6172
that.canEqual(this)
is not redundant. It is necessary in case that
is an instance of a subclass of Person
that has defined it's own equality (and it's own canEqual
method).
this.canEqual(that)
on the other hand would be redundant.
The main purpose is to ensure that the equality relation is valid in both directions between an instance of Person
and potentially a subclass of Person
that may have it's own implementation of equals.
Suppose I have:
class Person(...) {
... as defined, but without the `that.canEqual(this)` call
}
class Nobody extends Person {
// contrived, but valid definition
override def equals (that: Any) = false
... and some definition of hashCode that happens to produce same value
}
...
// then
new Person(...) == new Nobody // true
new Nobody == new Person(...) // false
// breaks equals by not being symmetric
More detailed explanation link provided by pedrofurla in the comments: http://www.artima.com/pins1ed/object-equality.html
Upvotes: 5
Reputation: 2803
Two different objects from two separate classes can have the same hash code; you therefore cannot say that two objects are equal based on their hash code alone. For example, a Dog
with yellow fur and a loud bark might have hashCode
of 20011 because of the way a Dog
defines hashCode
; a RocketShip
designed to fly to Saturn might also have a hashCode
of 20011 for a very unrelated reason. Clearly, these are two very different objects. If, however, two Dogs
or two RocketShips
have the same hash code then it is probably* safe to assume they're equivalent objects: it wouldn't make any sense to have to have two equivalent objects (e.g., two Dogs
with yellow fur and loud barks) have two different hash codes, and it wouldn't make sense to have two different objects of the same type (e.g., one Dog
with black fur, another with brown fur) have the same hash code. The isInstanceOf
check lets you ensure that you're dealing with two objects of the same type: the hash code check allows you to quickly assess whether or not two objects should be equivalent.
(*: If there are a finite number of hash codes and an infinite combination of attributes producing unique Dogs
, then there will inevitably be hash code collisions. Even the isInstanceOf
check isn't sufficient, but is probably good enough for most cases and hence in the cookbook as an example.)
Edit: See here:
Pitfall #2: Changing equals without also changing hashCode. If two objects are equal according to the equals method, then calling the hashCode method on each of the two objects must produce the same integer result
In short, two equal objects must have the same hash code to fulfill the method's contract (but nowhere does it say that two objects with equal hash codes must be equal!)
Edit 2 As you have a Java background, you're likely used to seeing something of the following.
public boolean equals(Object other) {
// I am a Dog. If they're not a dog, I can't be equal to them.
if (!(other instanceof Dog))
return false;
// If the other dog's properties are equal to my own, then we're equal.
if (...)
return true;
// If not, we're not equal.
else
return false;
}
The code sample you've given is doing (roughly) the same thing, with the hashCode
check substituting for an equality check.
Upvotes: 0