Entity Class Definition
Let's define a simple Rectangle class that overrides the equals method to compare objects based on their width and height values instead of reference equality.
class Rectangle {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
Rectangle other = (Rectangle) obj;
return width == other.width && height == other.height;
}
}
Testing Object Equality
When we create two Rectangle objects with the same width and height values, the equals method returns true as expected.
@Slf4j
public class EqualityDemo {
public static void testEquality() {
Rectangle rect1 = new Rectangle();
rect1.setWidth(10);
rect1.setHeight(5);
Rectangle rect2 = new Rectangle();
rect2.setWidth(10);
rect2.setHeight(5);
log.info("rect1.equals(rect2) is " + rect1.equals(rect2));
}
}
Output:
rect1.equals(rect2) is true
The Problem: Using HashSet
Now let's add these two logically equal objects to a HashSet and check if the set can find the second object.
@Slf4j
public class EqualityDemo {
public static void testHashSet() {
Rectangle rect1 = new Rectangle();
rect1.setWidth(10);
rect1.setHeight(5);
Rectangle rect2 = new Rectangle();
rect2.setWidth(10);
rect2.setHeight(5);
log.info("rect1.equals(rect2) is " + rect1.equals(rect2));
HashSet<Rectangle> shapes = new HashSet<>();
shapes.add(rect1);
log.info("shapes.contains(rect2) is " + shapes.contains(rect2));
}
}
Output:
rect1.equals(rect2) is true
shapes.contains(rect2) is false
This is unexpected! Two objects that are equal according to equals() cannot be found in a HashSet. This happens becuase hashCode() was not overridden. By default, each object receives a hash code based on its memory location, so rect1 and rect2 have different hash codes even though they are logically equal.
The Solution: Override hashCode
To fix this issue, we need to override the hashCode method to return the same value for objects with equal field values.
class Rectangle {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
Rectangle other = (Rectangle) obj;
return width == other.width && height == other.height;
}
@Override
public int hashCode() {
return Objects.hash(width, height);
}
}
Verification
After overriding hashCode, both HashSet and HashMap work correct with equal objects.
HashSet<Rectangle> shapes = new HashSet<>();
shapes.add(rect1);
log.info("shapes.contains(rect2) is " + shapes.contains(rect2));
HashMap<Rectangle, String> map = new HashMap<>();
map.put(rect1, "blue");
log.info("map.get(rect2) is " + map.get(rect2));
Output:
shapes.contains(rect2) is true
map.get(rect2) is blue
Without overriding hashCode, the HashMap lookup would return null becuase the two equal objects hash to different bucket locations.