Java | Overriding equal() Method

Overriding common methods (equals, hashCode, toString, and clone) of the Object class and Comparable.compareTo() method, should be done according to the general contracts provided in the documentation to ensure proper behaviour of these methods.
Overriding Object Common Methods
Objectis the super type for all classes.- All classes inherit the default behaviour of
Objectmethods (equals, hashCode, toString, and clone)
public boolean equals(Object obj)
public int hashCode()
public String toString()
protected Object clone() throws CloneNotSupportedException
- However, if you decide to override one of these methods, then there are general contracts or instructions you should follow to ensure proper execution of these methods.
Overriding equals(Object)
Default Implementation
- The default behaviour of equals(Object) method is to compare objects references “identity”.
public boolean equals(Object obj) {
return (this == obj);
}
- So, if a class creates a single instance “singleton” or unique instances, then it is better to keep default implementation.
- If we need to compare instances of a class based on their values “logical equality”, then overring equals(Object) is necessary.
General Contracts
- Overriding equals(Object) method should adhere to the general contract as documented in
Objectclass API.
The equals method implements an equivalence relation on non-null object references:
- It is reflexive: for any non-null reference value x, x.equals(x) should return true.
- It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
- For any non-null reference value x, x.equals(null) should return false.
Reflexive
myObject.equals(myObject) // should return true
Symmetry
myObject.equals(anotherObject) // returns true
anotherObject.equals(myObject) // should return true too!
We can demonstrate how to get this one wrong in the following example
MyStringclass overrides equals(Object). The method was overridden to handle passed object of typeStringwhich works fine. However,Stringclass was NOT implemented to handle an object of typeMyString! This implementation is violating symmetry.
public class MyString {
private final String s;
public MyString(String s) {
this.s = (s == null? "":s);
}
@Override
public boolean equals(Object obj) {
if(obj instanceof MyString)
return ((MyString)(obj)).s.equals(this.s);
else if(obj instanceof String)
return obj.equals(this.s);
else
return false;
}
}
public static void main(String[] args) {
MyString myString = new MyString("cool!");
String string = "cool!";
System.out.println(myString.equals(string)); // returns true
System.out.println(string.equals(myString)); // returns false
}
MyStringclass should not accept object of type other thanMyString.
@Override
public boolean equals(Object obj) {
if(obj instanceof MyString)
return ((MyString)(obj)).s.equals(this.s);
else
return false;
}
public static void main(String[] args) {
MyString myString = new MyString("cool!");
String string = "cool!";
System.out.println(myString.equals(string)); // returns false
System.out.println(string.equals(myString)); // returns false
}
Transitivity
firstObject.equals(secondObject) // returns true
secondObject.equals(thirdObject) // returns true
firstObject.equals(thirdObject) // must return true
We can demonstrate how to get this one wrong in the following example
- We have a class
Pointwith two parameters and overrides equals() method with no violation of general contracts. ColorPointis a subclass with one extra color parameter and also overrides equals() method that includes the color paramter in the logic of equality.
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 obj) {
if (!(obj instanceof Point))
return false;
Point p = (Point)obj;
return p.x == x && p.y == y;
}
}
public class ColorPoint extends Point {
private final String color;
public ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
@Override public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint))
return false;
return super.equals(obj) && ((ColorPoint) obj).color.equals(this.color);
}
}
ColorPointequals() method violates symmetry!
public static void main(String[] args) {
Point point = new Point(1, 2);
ColorPoint colorPoint = new ColorPoint(1, 2, "RED");
System.out.println(point.equals(colorPoint)); // true
System.out.println(colorPoint.equals(point)); // false
}
- To fix the symmetry issue we modify the equal() method as following
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point))
return false;
// if obj is Point but not ColorPoint
if (!(obj instanceof ColorPoint))
return obj.equals(this);
// if obj is a ColorPoint; do a full comparison
return super.equals(obj) && ((ColorPoint) obj).color.equals(this.color);
}
public static void main(String[] args) {
Point point = new Point(1, 2);
ColorPoint colorPoint = new ColorPoint(1, 2, "RED");
System.out.println(point.equals(colorPoint)); // true
System.out.println(colorPoint.equals(point)); // true
}
- However, this modified version of equals() method violates transitivity!

public static void main(String[] args) {
ColorPoint redPoint = new ColorPoint(1, 2, "RED");
Point point = new Point(1, 2);
ColorPoint bluePoint = new ColorPoint(1, 2, "BLUE");
System.out.println(redPoint.equals(point)); // true
System.out.println(point.equals(bluePoint)); // true
System.out.println(redPoint.equals(bluePoint)); // false
}
- It turns out that this is a fundamental problem in object-oriented languages.
“There is no way to extend an instantiable class and add a value component while preserving the equals contract” ~ Joshua Block’s, Effective Java
- The solution for this problem is to use composition instead of inheritance
public class ColorPoint {
private final Point point;
private final String color;
public ColorPoint(Point point, String color) {
this.point = point;
this.color = color;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint) obj;
return cp.point.equals(this.point) && cp.color.equals(this.color);
}
}
public static void main(String[] args) {
ColorPoint redPoint = new ColorPoint(new Point(1, 2), "RED");
Point point = new Point(1, 2);
ColorPoint bluePoint = new ColorPoint(new Point(1, 2), "BLUE");
System.out.println(redPoint.equals(point)); // false
System.out.println(point.equals(bluePoint)); // false
System.out.println(redPoint.equals(bluePoint)); // false
}
Consistency
- Calling equals() method on two objects should consistently return the same result until one or both of them is modified.
- This can be achieved by making the object immutable.