Leepoint Java Reference Notes on the Java language and its standard library.

Comparing objects

== asks whether two variables point at the same thing. Usually you meant something else.

For any object, == compares references. Whether two variables refer to the same object. equals compares whatever the class decides equality means.

Point p = new Point(3, 4);
Point q = new Point(3, 4);

p == q          // false, two objects
p.equals(q)     // true, if Point implements equals properly

For primitives (int, double, char, boolean) there are no references and == compares values, which is what you want.

The default is not what you expect

Object.equals compares references. So a class that does not override equals gets identity comparison, and two objects with identical contents are unequal. That is rarely the intent, and it fails quietly. The code compiles and produces wrong answers.

Writing equals

The contract is precise. equals must be reflexive, symmetric, transitive, consistent, and false for null.

@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;
}

The instanceof check handles null for free, null instanceof Anything is false, and the pattern variable saves the cast.

hashCode comes with it

If you override equals you must override hashCode. The contract: equal objects must have equal hash codes.

@Override
public int hashCode() {
    return Objects.hash(x, y);
}

Skip this and HashMap and HashSet break in a way that is genuinely hard to debug. Two equal objects land in different buckets, so an object put into a set is not found by an equal one, and contains returns false for something that is demonstrably in there.

The reverse is allowed: unequal objects may share a hash code. That is a collision, and the collection handles it.

Records do this for you

If the class is a plain carrier of values, a record generates equals, hashCode and toString from the components:

record Point(int x, int y) { }

That is the right default for value types, and it removes the most common source of this bug.

Ordering

equals gives you equality. For sorting, implement Comparable:

@Override
public int compareTo(Point other) {
    return Integer.compare(this.x, other.x);
}

Use Integer.compare rather than subtracting. a - b overflows for large values and produces the wrong sign, which is a bug that surfaces years later on unusual data.

Keep compareTo consistent with equals where you can: compareTo returning zero should normally mean equals returns true. Sorted collections assume it, and TreeSet will silently treat two objects as duplicates if it does not hold.