Why we must obey the general contract when overriding equals?
For Java programmers, it seems simple to override the equals method, but there are many ways to get it wrong, and consequences can be dire. In some cases, the easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself.
However, in some cases, we must override the equals method. So…
When is it appropriate to override equals?
It is when a class has a notion of logical equality that differs from mere object identity and a superclass has not already overridden equals. This is generally the case for value classes. A value class is simply a class that represents a value, such as an Integer or String.
A programmer who compares references to value objects using the equals method expects to find out whether they are logically equivalent, not whether they refer to the same object.
Not only is overriding the equals method necessary to satisfy programmer expectations, but it also enables instances to serve as map keys or set elements with predictable, desirable behavior.
There are five contracts that help us to avoid these problems when overriding the equals method.
So, how to override the equals method?
How to override the equals method?
When you override the equals method, you must adhere to its general contract. Here is the contract, from the specification for Object :
The equals method implements an equivalence relation. It has these properties:
• Reflexive: For any non-null reference value x, x.equals(x) must return true.
• Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true.
• Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.
• Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) must consistently return true or consistently return false, provided no information used in equals comparisons is modified.
• For any non-null reference value x, x.equals(null) must return false.
An example to override the equals method:
// Class with a typical equals method
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "area code");
this.prefix = rangeCheck(prefix, 999, "prefix");
this.lineNum = rangeCheck(lineNum, 9999, "line num");
}
private static short rangeCheck(int val, int max, String arg) {
if (val < 0 || val > max) throw new IllegalArgumentException(arg + ": " + val);
return (short) val;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNum == lineNum && pn.prefix == prefix
&& pn.areaCode == areaCode;
} ... // Remainder omitted
}
A few caveats:
- Always override hashCode when you override equals.
- Don’t substitute another type for Object in the equals declaration.
- Don’t try to be too clever.
In conclusion, don’t override the equals method unless you have to: in many cases, the implementation inherited from Object does exactly what you want. If you do override equals, make sure to compare all of the class’s significant fields and to compare them in a manner that preserves all five provisions of the equals contract.