Java equals, hashCode, and toString
Define when two objects are equal so that they work correctly in sets and maps.
import java.util.*;
class Main {
static class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof Point)) {
return false;
}
Point p = (Point) other;
return x == p.x && y == p.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
}
public static void main(String[] args) {
Point a = new Point(1, 2);
Point b = new Point(1, 2);
System.out.println((a == b) + " " + a.equals(b));
Set<Point> seen = new HashSet<>();
seen.add(a);
seen.add(b);
seen.add(new Point(3, 4));
System.out.println(seen.size() + " " + seen.contains(new Point(3, 4)));
System.out.println(a + " " + (a.hashCode() == b.hashCode()));
}
}false true 2 true (1,2) true
Explanation
By default, two objects are equal only if they are the same object. A class that represents a value, such as a point, should override equals so that two points with the same coordinates count as equal. It must then also override hashCode so that equal objects have the same hash code, otherwise hash-based collections such as HashSet and HashMap behave incorrectly.
The contract is: equal objects have equal hash codes, equals is reflexive, symmetric, and transitive, and the result does not change while the object is unchanged. Objects.equals and Objects.hash make a correct implementation short. toString should return a readable description, because it is used when an object is printed or logged.
Explore the dry run
import java.util.*; class Main { static class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof Point)) { return false; } Point p = (Point) other; return x == p.x && y == p.y; } @Override public int hashCode() { return Objects.hash(x, y); } @Override public String toString() { return "(" + x + "," + y + ")"; } } public static void main(String[] args) { Point a = new Point(1, 2); Point b = new Point(1, 2); System.out.println((a == b) + " " + a.equals(b)); Set<Point> seen = new HashSet<>(); seen.add(a); seen.add(b); seen.add(new Point(3, 4)); System.out.println(seen.size() + " " + seen.contains(new Point(3, 4))); System.out.println(a + " " + (a.hashCode() == b.hashCode())); }}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Two points are equal when their coordinates are equal, not when they are the same object. | — | — | |
| Equal points must give the same hash code. | — | — | |
| == compares references, equals compares coordinates. | — | false true | |
| b equals a and has the same hash code, so the set does not add it again. | — | false true | |
| The set holds two distinct points, and a new equal point is found. | — | false true 2 true | |
| toString gives a readable form; equal objects have equal hash codes. | — | false true 2 true (1,2) true |
Cost and common mistakes
equals and hashCode are O(1) here; set operations are O(1) on average.
- Overriding equals without hashCode breaks HashSet and HashMap: equal points could land in different buckets.
- Do not use mutable fields in equals and hashCode of keys. Changing a key inside a set makes it unfindable.
- The parameter of equals must be Object. A method equals(Point) is an overload, not an override.
Try it yourself
What would the set size be without the hashCode override?
Reveal answer and reasoning
Probably 3, because a and b would usually have different identity hash codes and land in different buckets, so both would be kept.