A key facet of the equals
contract is that if a.equals(b)
then b.equals(a)
, i.e. that the relationship is
symmetric.
Using instanceof
breaks the contract when there are subclasses, because while the child is an instanceof
the parent, the
parent is not an instanceof
the child. For instance, assume that Raspberry extends Fruit
and adds some fields (requiring a
new implementation of equals
):
Fruit fruit = new Fruit();
Raspberry raspberry = new Raspberry();
if (raspberry instanceof Fruit) { ... } // true
if (fruit instanceof Raspberry) { ... } // false
If similar instanceof
checks were used in the classes' equals
methods, the symmetry principle would be broken:
raspberry.equals(fruit); // false
fruit.equals(raspberry); //true
Additionally, non final
classes shouldn’t use a hardcoded class name in the equals
method because doing so breaks the
method for subclasses. Instead, make the comparison dynamic.
Further, comparing to an unrelated class type breaks the contract for that unrelated type, because while
thisClass.equals(unrelatedClass)
can return true, unrelatedClass.equals(thisClass)
will not.
Noncompliant code example
public class Fruit extends Food {
private Season ripe;
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (Fruit.class == obj.getClass()) { // Noncompliant; broken for child classes
return ripe.equals(((Fruit)obj).getRipe());
}
if (obj instanceof Fruit ) { // Noncompliant; broken for child classes
return ripe.equals(((Fruit)obj).getRipe());
}
else if (obj instanceof Season) { // Noncompliant; symmetry broken for Season class
// ...
}
//...
Compliant solution
public class Fruit extends Food {
private Season ripe;
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() == obj.getClass()) {
return ripe.equals(((Fruit)obj).getRipe());
}
return false;
}