LearnFrenzy
Sign in Take a free test

Inheritance and Polymorphism · Lesson 25 of 42

The Object Class: equals, hashCode, toString

5 min read Updated Nov 7, 2026 Saurabh Samir

Every Java class extends java.lang.Object, and inherits its methods. Three of them you will override in almost every data class: toString(), equals() and hashCode(). Getting the last two wrong causes some of the most confusing bugs in Java – objects that "disappear" from a HashSet or HashMap.

In this lesson

  • The methods every object inherits
  • Overriding toString()
  • equals(): the contract and a correct implementation
  • Why hashCode() must agree with equals()

What Object gives you

MethodDefault behaviour
toString()ClassName@hexHash – not useful for people
equals(Object o)same as ==: true only for the same object
hashCode()a number derived from object identity
getClass()the runtime class
wait(), notify(), notifyAll()low-level thread coordination
clone(), finalize()legacy – rarely used today

The problem with the defaults

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Point a = new Point(2, 3);
        Point b = new Point(2, 3);
        System.out.println(a.equals(b));          // false: different objects
        Set<Point> set = new HashSet<>();
        set.add(a);
        set.add(b);
        System.out.println(set.size());           // 2 "duplicates"
        System.out.println(a.toString().startsWith("Point@"));
    }
}

class Point {
    final int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

Output

false
2
true

Two points with the same coordinates should be equal. For that we override all three methods.

A correct implementation

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Point a = new Point(2, 3), b = new Point(2, 3);
        System.out.println(a.equals(b) + " " + (a.hashCode() == b.hashCode()));
        Set<Point> set = new HashSet<>(Set.of(a));
        System.out.println(set.contains(b) + " " + set.size());
        Map<Point, String> labels = new HashMap<>();
        labels.put(a, "start");
        System.out.println(labels.get(new Point(2, 3)));
        System.out.println(a);
    }
}

class Point {
    private final int x, y;

    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;                         // same object
        if (!(o instanceof Point other)) return false;      // null or another type
        return x == other.x && y == other.y;                // compare the fields that matter
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);                          // same fields as equals
    }

    @Override
    public String toString() {
        return "Point(" + x + ", " + y + ")";
    }
}

Output

true true
true 1
start
Point(2, 3)

The equals contract

The Java documentation requires equals to be:

  • Reflexive: a.equals(a) is true.
  • Symmetric: if a.equals(b) then b.equals(a).
  • Transitive: if a equals b and b equals c, then a equals c.
  • Consistent: repeated calls give the same answer while the objects don't change.
  • a.equals(null) is always false.

Why hashCode must match equals

Hash-based collections first use hashCode() to pick a bucket, then use equals() to compare only inside that bucket.

set.contains(new Point(2, 3))

  1. hashCode()→ bucket number
  2. Go to that bucketonly a few objects are there
  3. equals() each onetrue → found

If equal objects produced different hash codes, Java would look in the wrong bucket and never find the match:

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static void main(String[] args) {
        Set<Badge> set = new HashSet<>();
        set.add(new Badge("JAVA"));
        System.out.println(new Badge("JAVA").equals(new Badge("JAVA")));
        System.out.println(set.contains(new Badge("JAVA")));   // almost always false!
    }
}

class Badge {
    final String code;
    Badge(String code) { this.code = code; }

    @Override
    public boolean equals(Object o) {
        return o instanceof Badge b && code.equals(b.code);
    }
    // hashCode() NOT overridden – breaks hash collections
}

Output

true
false

The rule

If two objects are equal according to equals(), they must have the same hashCode(). (The reverse isn't required: different objects may share a hash code – that's a collision.) Override both together, using the same fields – IDEs generate them, and records (chapter 10) do it automatically.

Common mistake: mutable keys

If you change a field used in hashCode() after putting the object in a HashSet or using it as a HashMap key, it is now in the wrong bucket and effectively lost. Use immutable objects as keys.

Helpful utilities

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        String a = null, b = "java";
        System.out.println(Objects.equals(a, b));        // null-safe
        System.out.println(Objects.equals(null, null));
        System.out.println(Objects.hash("Asha", 21) == Objects.hash("Asha", 21));
        System.out.println(Objects.requireNonNullElse(a, "default"));
    }
}

Output

false
true
true
default

Practice

Try it yourself

  1. Write a Student class whose equality is based only on rollNo, and show that a HashSet keeps one copy.

    Show solution
    import java.util.HashSet;
    import java.util.Set;
    
    public class Main {
        public static void main(String[] args) {
            Set<Student> set = new HashSet<>();
            set.add(new Student(21, "Ira"));
            set.add(new Student(21, "Ira K."));
            System.out.println(set.size());
        }
    }
    
    class Student {
        final int rollNo;
        final String name;
        Student(int rollNo, String name) { this.rollNo = rollNo; this.name = name; }
        @Override public boolean equals(Object o) { return o instanceof Student s && s.rollNo == rollNo; }
        @Override public int hashCode() { return Integer.hashCode(rollNo); }
    }

    Output

    1

Quick check

What does the default equals() compare?

Object identity – the same as ==.

If you override equals but not hashCode, what breaks?

Hash-based collections (HashSet, HashMap) can't find equal objects because they look in different buckets.

Can two unequal objects have the same hashCode?

Yes – that's a collision, and it's allowed.

Summary

  • Every class inherits from Object; override toString() for readable output.
  • Override equals() with the contract in mind, and always override hashCode() with the same fields.
  • Use Objects.equals and Objects.hash; keep hash keys immutable.

Interview practice: Java OOP in depth.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular