Item 08 - Obey the general contract when overriding equals
From Effective Java 2/e by Joshua Bloch
Conditions which you don't need to override equals method
- Each instance of the class is inherently unique
- You don’t care whether the class provides a “logical equality” test
- A super class has already overridden equals, and the super class behavior is appropriate for this class
- The class is private or package-private, and you are certain that it sequals method will never be invoked
Appropriate time to override equals method
- a class has a notion of logical equality that differs from mere object identity
- a superclass has not already overridden equals to implement the desired behavior
General Contract to override equals method
- Reflexive
x.equals(x) = true - Symmetric
x.equals(y) = y.equals(x) - Transitive
x.equals(y) = y.equals(z) true => z.equals(x) = true - Consistent
x.equals(y) always has to be same if x, y value remains same
Symmetric violation example
// Broken - violates symmetry!
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
// Broken - violates symmetry!
@Override public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(
((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
... // Remainder omitted
}
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
cis.equals(s) == true;
s.equals(cis) == false;
There is no way to extend an instantiable class and add a value component while preserving the equals
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y; }
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
... // Remainder omitted
}
public class ColorPoint extends Point {
private final Color color;
public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}
... // Remainder omitted
}
// Broken - violates symmetry!
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
// Broken - violates transitivity!
@Override public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
// If o is a normal Point, do a color-blind comparison
if (!(o instanceof ColorPoint))
return o.equals(this);
// o is a ColorPoint; do a full comparison
return super.equals(o) && ((ColorPoint)o).color == color;
}
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
p1.equals(p2) == true;
p2.equals(p3) == true;
p1.equals(p3) == false;
Null check - Use instanceof
@Override public boolean equals(Object o) {
// if o is null, instanceof will return false
if (!(o instanceof MyType))
return false;
MyType mt = (MyType) o;
...
}
Recipe for a high-quality equals method
- Use the == operator to check if the argument is a reference to this object
Use the instanceof operator to check if the argument has the correct type
@Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PhoneNumber)) return false; PhoneNumber pn = (PhoneNumber)o; return pn.lineNumber == lineNumber && pn.prefix == prefix && pn.areaCode == areaCode; }Cast the argument to the correct type
- For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object
- When you are finished writing your equals method, ask yourself three questions: Is it symmetric? Is it transitive? Is it consistent?