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?