The Object Class in Java – Mastering equals(), hashCode(), and toString()

Illustration for The Object Class in Java – Mastering equals(), hashCode(), and toString()
By Last updated:

Introduction

Everything in Java extends from a single class: java.lang.Object. Whether you're writing a basic class or using advanced frameworks like Spring or Hibernate, you're always dealing with the Object class behind the scenes.

Three of its most commonly used methods are:

  • equals() – used for comparing objects
  • hashCode() – used in hashing collections like HashMap
  • toString() – used for printing/debugging

Overriding these methods properly is essential for writing clean, correct, and scalable code. In this guide, we’ll dive deep into how they work, when to override them, and how they impact your Java applications.


What is the Object Class?

The Object class is the root of the Java class hierarchy. All classes—built-in or user-defined—implicitly extend it.

class MyClass {
    // implicitly extends Object
}

Methods in the Object Class

Key methods include:

  • equals(Object obj)
  • hashCode()
  • toString()
  • clone()
  • getClass()
  • notify(), notifyAll(), wait()

This article focuses on the three most used and most frequently overridden: equals(), hashCode(), and toString().


equals() Method

Purpose

Checks whether two objects are logically equal (not just reference equal).

Default Behavior

public boolean equals(Object obj) {
    return (this == obj);
}

When to Override

When comparing object content instead of reference.

Custom equals() Example

class User {
    String name;

    User(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        User user = (User) obj;
        return name.equals(user.name);
    }
}

hashCode() Method

Purpose

Returns an integer hash code used in hash-based collections like HashMap, HashSet.

Default Behavior

Generates unique hash per object (usually based on memory address).

Contract with equals()

  • If a.equals(b), then a.hashCode() == b.hashCode() must be true
  • If a.hashCode() != b.hashCode(), then a.equals(b) can be false

Custom hashCode() Example

@Override
public int hashCode() {
    return name.hashCode();
}

Use Objects.hash(...) for multiple fields:

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

toString() Method

Purpose

Returns a string representation of the object (used in logging, debugging, printing).

Default Behavior

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

Overriding Example

@Override
public String toString() {
    return "User{name='" + name + "'}";
}

UML Representation

<<class>> Object
+ equals(Object): boolean
+ hashCode(): int
+ toString(): String

<<class>> User extends Object
+ equals(Object): boolean
+ hashCode(): int
+ toString(): String

Real-World Example: HashMap with Custom Keys

Map<User, String> map = new HashMap<>();

User u1 = new User("Alice");
User u2 = new User("Alice");

map.put(u1, "Engineer");

// Without equals() and hashCode(), u2 will not be found
System.out.println(map.get(u2)); // null

✅ Fix: Override equals() and hashCode() properly.


Java 16+ Records and Object Methods

Java records auto-generate:

  • equals()
  • hashCode()
  • toString()
record User(String name, int age) {}

Prints:

User[name=Alice, age=30]

Common Pitfalls

❌ Inconsistent equals() and hashCode()

Overriding equals() without hashCode() breaks collections.

❌ Reference Comparison in equals()

Use .equals() for object fields, not ==.


Best Practices

  • Always override hashCode() if you override equals()
  • Use Objects.equals() and Objects.hash() for null-safety
  • Use IDEs to generate boilerplate safely
  • Prefer records if you need value classes with equals/hashCode

Real-World Analogy

  • equals() is like comparing the contents of two books.
  • hashCode() is like the library catalog number—helps you find the book faster.
  • toString() is like the book summary on the back cover.

Conclusion

The Object class forms the core of Java's object model. Overriding equals(), hashCode(), and toString() gives your objects identity, predictability, and readability.

Whether you're using collections, debugging, or modeling business entities, these methods help build robust and maintainable software.


Key Takeaways

  • All Java classes inherit from Object
  • equals() compares content; == compares references
  • hashCode() must align with equals() to work with collections
  • toString() improves debugging and logging
  • Java records provide default implementations of all three

FAQs

1. Is equals() the same as ==?
No. equals() checks content; == checks references.

2. What happens if I override equals() but not hashCode()?
You break the contract—collections like HashSet may not work correctly.

3. Can I override equals() using instanceof?
Yes, but prefer getClass() if you need strict type checks.

4. Are hashCode() values unique?
Not necessarily—collisions are allowed but should be minimized.

5. Why should I override toString()?
To get human-readable output for logging and debugging.

6. Can I use == for string comparison?
No. Use .equals() for value comparison.

7. Does record override equals() and hashCode()?
Yes. Records auto-generate value-based equality methods.

8. What is the default toString() output?
It returns something like User@7ad041f3.

9. Is Objects.hash() efficient?
Yes, and it simplifies multi-field hash generation.

10. Do I need to override equals() in every class?
No. Only override it if you plan to compare object content or use the class in collections.