Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Equality and hash codes

Java has two kinds of equality. == checks that two variables point to the same object in memory. The equals method checks that objects are equal in meaning — for example, two strings consist of the same characters. Collections like HashSet and HashMap also rely on the hash code — a number an object returns from its hashCode() method.

== and equals

Let's create two identical strings. These are two different objects with the same contents:

void main() {
    String a = new String("cat");
    String b = new String("cat");
 
    IO.println(a == b);
    IO.println(a.equals(b));
}

Output:

false
true

== returned false: the objects are different. equals returned true: the contents match. That's why strings and other objects are always compared with equals, and == is left for primitives such as int.

The default equals

If a class doesn't override equals, it inherits the version from Object, which works like ==. Two "identical" objects are considered different:

class Point {
    final int x;
    final int y;
 
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
 
void main() {
    Point p1 = new Point(1, 2);
    Point p2 = new Point(1, 2);
 
    IO.println(p1.equals(p2));
}

Output:

false

Your own equals and hashCode

To compare objects by their fields, you override equals. Along with it you must override hashCode. The rule (the contract) is: if two objects are equal by equals, their hashCode values must match. Handy helpers are Objects.equals (compares safely even with null) and Objects.hash:

class Point {
    final int x;
    final int y;
 
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point other)) return false;
        return x == other.x && y == other.y;
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}
 
void main() {
    Point p1 = new Point(1, 2);
    Point p2 = new Point(1, 2);
 
    IO.println(p1.equals(p2));
    IO.println(p1.hashCode() == p2.hashCode());
}

Output:

true
true

equals takes an Object, so we check the type first — the instanceof pattern comes in handy here.

Why hashCode is needed

HashSet and HashMap sort objects into "buckets" by their hash code. To find an object, the collection first computes the hash code, goes to the right bucket and only there compares with equals. That's why equal objects must have equal hash codes — otherwise the collection looks in the wrong bucket. Different objects, on the other hand, may happen to share a hash code — that's fine:

void main() {
    IO.println("Aa".hashCode());
    IO.println("BB".hashCode());
    IO.println("Aa".equals("BB"));
}

Output:

2112
2112
false

Records do it themselves

Records get equals, hashCode and toString generated automatically from all their components. That makes records the best choice for objects compared by value:

record Point(int x, int y) {}
 
void main() {
    Set<Point> visited = new HashSet<>();
    visited.add(new Point(1, 2));
    visited.add(new Point(1, 2));
    visited.add(new Point(3, 4));
 
    IO.println(visited.size());
    IO.println(visited.contains(new Point(3, 4)));
}

Output:

2
true

Common mistake

If you override only equals and forget hashCode, the objects are equal but have different hash codes. A HashSet puts them into different buckets and treats them as different:

class Point {
    final int x;
    final int y;
 
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
    @Override
    public boolean equals(Object o) {
        return o instanceof Point other && x == other.x && y == other.y;
    }
}
 
void main() {
    Set<Point> points = new HashSet<>();
    points.add(new Point(1, 2));
    points.add(new Point(1, 2));
 
    IO.println(new Point(1, 2).equals(new Point(1, 2)));
    IO.println(points.size());
    IO.println(points.contains(new Point(1, 2)));
}

Output:

true
2
false

There's no compile error, but the collection misbehaves. Always override equals and hashCode together — or use records, which do it for you.

Thanks for reading!

Donate to the site — we work hard for you.