Overriding the equals() and hashCode() methods

The equals() method is inherited from the superclass Object and determines if two instances are equal or equivalent.
Usually this method should be overridden because the equals() method of the Object class is equivalent to the operator ==, which returns true if and only if the two references refer to the same object.
Look at the following code:

  • file eu/lucazanini/circle/Main.java
    package eu.lucazanini.circle;
    
    import eu.lucazanini.circle.Circle.Color;
    
    public class Main {
    
        public static void main(String[] args) {
    
    	Circle circle1 = new Circle(5, Color.RED);
    	Circle circle2 = new Circle(5, Color.RED);
    	Circle circle1_copy = circle1;
    
    	System.out.println("circle1 == circle1_copy is "
    		+ (circle1 == circle1_copy));
    	System.out.println("circle1 == circle2 is " + (circle1 == circle2));
    	System.out.println("circle1.equals(circle2) is "
    		+ (circle1.equals(circle2)));
        }
    }
    
  • file eu/lucazanini/circle/Circle.java
    package eu.lucazanini.circle;
    
    public class Circle {
    
        public enum Color {
    	BLUE(3), GREEN(2), RED(1);
    
    	private int colorCode;
    
    	Color(int colorCode) {
    	    this.colorCode = colorCode;
    	}
    
    	public int getColorCode() {
    	    return colorCode;
    	}
        }
    
        private Color color;
        private double radius;
    
        public Circle(double radius, Color color) {
    	this.radius = radius;
    	this.color = color;
        }
    
        public Color getColor() {
    	return color;
        }
    
        public double getRadius() {
    	return radius;
        }
    
        public void setColor(Color color) {
    	this.color = color;
        }
    
        public void setRadius(double radius) {
    	this.radius = radius;
        }
    
    }
    

The output of this example is:

circle1 == circle1_copy is true
circle1 == circle2 is false
circle1.equals(circle2) is false

If you want the equals() returns true when the circles have the same radius and color, then you need to override it in the Circle class:

    @Override
    public boolean equals(Object obj) {
	if ((obj instanceof Circle)) {
	    Circle circle = (Circle) obj;
	    if (radius == circle.getRadius() && color.equals(circle.getColor())) {
		return true;
	    } else {
		return false;
	    }
	} else {
	    return false;
	}
    }

The resulting output is:

circle1 == circle1_copy is true
circle1 == circle2 is false
circle1.equals(circle2) is true

Note that it is good practice to verify that the object passed to equals() is the desired type (obj instanceof Circle), and only then verify that the condition for equality (radius == circle.getRadius() && color.equals(circle.getColor())).
The condition for equality must comply with the equals() contract that must be:

  • reflexive: ∀ x, x.equals(x) == true
  • symmetric: ∀ x y, x.equals(y) ⇔ y.equals(x)
  • transitive: ∀ x y, x.equals(y) and y.equals(z) ⇔ x.equals(z)
  • consistent: for any x e y multiple invocations of x.equals(y) always returns the same value if no information about x and y used in equals() is modified
  • ∀ x not null, x.equals(null) == false

When 2 objects are equal according to the equals() then also their hash codes must be equal, so it is a good practice, when you override equals(), also override hashCode(), a method inherited from the superclass Object.
In the implementation in Object, hashCode() returns an integer different for each object, but in general it is possible that different objects share the same hash code although this makes the code less efficient.
Another advantage in the override hashCode() is a more efficient search in Collections as HashMap or HashSet that use the hash code in the management of objects.
The contract for hashCode() with the following points:

  • consistent: hashCode() always returns the same integer for the same object if the information used in equals() is not modified (the integer returned can change between different executions of the application)
  • ∀ x y, x.equals(y) == true ⇔ hashCode(x) == hashCode(y)
  • x.equals(y) == false does not imply that the hash codes are different

Also hashCode() should use the same information used by equals().
Look at the implementation of equals() and hashCode() in this example:

  • file eu/lucazanini/circle/Main.java
    package eu.lucazanini.circle;
    
    import eu.lucazanini.circle.Circle.Color;
    
    public class Main {
    
        public static void main(String[] args) {
    
    	Circle circle1 = new Circle(5, Color.RED);
    	Circle circle2 = new Circle(5, Color.RED);
    	Circle circle1_copy = circle1;
    	Circle circle3 = new Circle(4, Color.GREEN);
    	Circle circle4 = new Circle(5.1, Color.RED);
    
    	System.out.println("circle1 == circle1_copy is "
    		+ (circle1 == circle1_copy));
    	System.out.println("circle1 == circle2 is " + (circle1 == circle2));
    	System.out.println("circle1.equals(circle2) is "
    		+ (circle1.equals(circle2)));
    
    	System.out.println("circle1.hashCode() is " + circle1.hashCode());
    	System.out.println("circle2.hashCode() is " + circle2.hashCode());
    	System.out.println("circle3.hashCode() is " + circle3.hashCode());
    	System.out.println("circle4.hashCode() is " + circle4.hashCode());
    
    	System.out.println("circle1.equals(circle3) is "
    		+ (circle1.equals(circle3)));
    	System.out.println("circle1.equals(circle4) is "
    		+ (circle1.equals(circle4)));
        }
    }
    
  • file eu/lucazanini/circle/Circle.java
    package eu.lucazanini.circle;
    
    public class Circle {
    
        public enum Color {
    	BLUE(3), GREEN(2), RED(1);
    
    	private int colorCode;
    
    	Color(int colorCode) {
    	    this.colorCode = colorCode;
    	}
    
    	public int getColorCode() {
    	    return colorCode;
    	}
        }
    
        private Color color;
        private double radius;
    
        public Circle(double radius, Color color) {
    	this.radius = radius;
    	this.color = color;
        }
    
        @Override
        public boolean equals(Object obj) {
    	if ((obj instanceof Circle)) {
    	    Circle circle = (Circle) obj;
    	    if (radius == circle.getRadius() && color.equals(circle.getColor())) {
    		return true;
    	    } else {
    		return false;
    	    }
    	} else {
    	    return false;
    	}
        }
    
        @Override
        public int hashCode() {
    	return (int) radius + getColor().getColorCode();
        }
    
        public Color getColor() {
    	return color;
        }
    
        public double getRadius() {
    	return radius;
        }
    
        public void setColor(Color color) {
    	this.color = color;
        }
    
        public void setRadius(double radius) {
    	this.radius = radius;
        }
    
    }
    

The output is:

circle1 == circle1_copy is true
circle1 == circle2 is false
circle1.equals(circle2) is true
circle1.hashCode() is 6
circle2.hashCode() is 6
circle3.hashCode() is 6
circle4.hashCode() is 6
circle1.equals(circle3) is false
circle1.equals(circle4) is false

Note that circles with different radius or color may have the same hash code but equal circles must always have the same hash code.
There are other ways of implementing hashCode(), also an implementation that always returns the same integer as in the following example is legal:

public int hashCode() {
	return 1;
}

This example does not violate the hashCode() contract but it is a very inefficient and useless implementation.

References:
Object as a Superclass


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.