Java Tutorial Series: This lesson is part of our comprehensive Complete Java Tutorial: Learn Java from Beginner to Advanced.

Java Equality and Reference Comparison: ==, equals() and Object Identity

Equality becomes especially important when Java programs work with objects. Primitive equality, reference identity, and logical equality are different concepts.

What You Will Learn

  • Primitive equality
  • Reference identity
  • equals()
  • String comparison
  • hashCode contract
  • Custom equality
  • Common mistakes

1. Primitive Equality

2. Reference Identity

String a = new String("Java");
String b = new String("Java");
System.out.println(a == b);

The expression checks whether the references identify the same object.

3. equals()

System.out.println(a.equals(b));

equals expresses logical equality as defined by the class. String overrides it for content comparison.

4. Null-Safe Equality

Calling equals on a null reference throws NullPointerException. When null values are possible, guard explicitly or use a null-safe utility such as Objects.equals.

5. equals and hashCode

Equal objects must have equal hash codes. This contract matters for hash-based collections.

6. Custom Classes

Domain classes should define equality according to their identity rules. Not every field necessarily belongs in equality.

7. Quick Comparison

Goal Typical approach
Primitive values ==
String content equals()
Logical object equality equals()
Same object identity ==

8. Mutable Equality State

Changing fields that participate in equality while an object is stored in a hash-based collection can produce difficult bugs.

9. Common Mistakes

  • Using == for String content
  • Calling equals on a possibly null reference
  • Overriding equals without hashCode
  • Defining equality without considering the domain

4. String Equality: Why equals() Matters

String is one of the most common places where beginners encounter the difference between identity and logical equality. Two String objects can contain the same characters while being different objects.

String a = new String("Java");
String b = new String("Java");

System.out.println(a == b);
System.out.println(a.equals(b));

The first expression asks whether a and b are the same object. The second asks whether their contents are equal according to String’s equals implementation.

5. The equals() Contract

For classes that override equals(), equality should follow a consistent contract. At a high level, equality should be reflexive, symmetric, transitive, consistent, and false when compared with null. A correct equals implementation is important when objects represent values.

6. hashCode() and Equality

When a class overrides equals(), it should also provide a compatible hashCode() implementation. Hash-based collections such as HashMap and HashSet depend on this relationship.

class User {
    private final String email;

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

    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof User)) return false;
        User user = (User) other;
        return java.util.Objects.equals(email, user.email);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(email);
    }
}

The important rule is that objects considered equal should produce the same hash code. Violating this rule can cause confusing behavior in hash-based collections.

7. Objects.equals() for Null-Safe Equality

boolean same = java.util.Objects.equals(firstName, secondName);

Objects.equals handles the cases where both values are null, one is null, or both are non-null. It is often cleaner than manually writing multiple null checks.

8. Records and Value-Oriented Equality

Java records are designed for concise data-oriented classes and provide generated implementations for methods including equals(), hashCode(), and toString(). This makes records useful when equality should reflect the record components.

record Product(String code, double price) {}

Product p1 = new Product("A1", 99.0);
Product p2 = new Product("A1", 99.0);
System.out.println(p1.equals(p2));

9. Identity in Caching and Object Management

Identity comparisons can be appropriate when the exact object instance matters, such as checking whether two references point to the same sentinel or managed object. It should not be used as a substitute for value equality when objects represent data.

10. Equality in Collections

Collections frequently depend on equality. List implementations generally use equals when searching for matching elements, while Set and Map implementations use equality and hashing according to their specific contracts.

List<String> names = List.of("Ravi", "Neha");
boolean found = names.contains(new String("Ravi"));

The contains operation succeeds because String defines logical equality by content. Understanding equals therefore helps explain why collection operations behave as they do.

11. Common Equality Mistakes

  • Using == to compare String contents.
  • Overriding equals() without a compatible hashCode().
  • Comparing mutable fields carelessly when objects are used as hash keys.
  • Assuming every class automatically has value-based equality.
  • Ignoring null when equality checks can receive missing values.

12. Practical Equality Checklist

  • Use == for primitive value comparison and intentional reference identity.
  • Use equals() for logical object equality when the class defines meaningful equality.
  • Use Objects.equals() when null-safe comparison is useful.
  • Keep equals() and hashCode() consistent.
  • Be careful when mutable objects are used as keys in hash-based collections.

13. Equality in Domain Models

Before overriding equals(), decide what makes two instances represent the same logical entity. For a value object, fields such as amount, currency, or coordinates may define equality. For an entity, an application may instead define identity around a stable identifier. Equality should reflect the domain model rather than simply comparing every field without thought.

14. Mutable Objects and Hash-Based Collections

Using a mutable object as a HashMap key can cause problems if fields involved in hashCode() change after insertion. The object may no longer be found in the expected bucket because its hash code has changed.

For hash-based keys, prefer stable equality-defining state. This is one reason immutable value objects are often easier to use safely in collections.

15. Equality and Inheritance

Equality becomes more subtle when inheritance is involved. Subclasses can introduce additional state, and poorly designed equals implementations can violate symmetry or transitivity. Keep equality rules deliberate and test them with representative parent and child objects.

16. Equality Tests

Unit tests are useful for verifying equality behavior. Test the same object, equal objects, different objects, null, a different type, and hash-code consistency.

assert user.equals(user);
assert user.equals(new User("ravi@example.com"));
assert !user.equals(null);

17. Practice Tasks for Equality

  • Compare two primitive values with ==.
  • Compare two separately created Strings using == and equals().
  • Create a small value class and define logical equality.
  • Implement hashCode() consistently with equals().
  • Use Objects.equals() for a null-safe comparison.
  • Place equal objects in a HashSet and observe why hashCode matters.

18. Equality Review Checklist

  • Decide whether the requirement is identity or logical equality.
  • Use == intentionally for primitives and identity checks.
  • Use equals() for value-based object comparison.
  • Keep hashCode() consistent with equals().
  • Avoid mutable equality keys in hash-based collections unless their state is carefully controlled.

Practical Mini Project

Create a small User value object with an email field. Implement equals and hashCode consistently. Create two objects containing the same email and test ==, equals, and their behavior in a HashSet. Then compare null values with Objects.equals. This exercise demonstrates identity, logical equality, and hashing in one practical example.

Equality Mini Project

A useful extension is to improve the first version rather than immediately building something large. Give each important value a clear name, keep methods short, validate inputs at the boundary, and test normal, boundary, and invalid cases. This turns a language concept into a small engineering habit.

Equality questions to practice

  • What is object identity?
  • What does equals mean?
  • Why must hashCode agree with equals?
  • Why should String content be compared with equals?
  • What problems can mutable hash keys cause?

How This Topic Connects to the Next Java Concepts

After equality, input/output provides the next practical step: receiving values from users and external sources and then applying the language rules learned so far.

19. Equality Testing Strategy

A good equality test suite should verify the contract rather than only one example. Check reflexive equality with the same object, equality between separate objects with the same logical state, inequality for different state, comparison with null, and comparison with another class. When hash-based collections are involved, also verify that equal objects behave consistently as keys or set elements.

20. Equality and Immutability

Immutable value objects make equality easier to reason about because the fields that define equality cannot unexpectedly change after the object is placed into a collection. When designing a value class, keep equality-defining state stable and avoid exposing mutable internals that can change the meaning of the object after construction.

Frequently Asked Questions

Should I use == for Strings?

No; use equals for content.

What does == mean for objects?

Reference identity.

Why does hashCode matter?

Hash-based collections rely on the equality/hashCode contract.

Can equals be customized?

Yes, classes can define logical equality appropriately.

Conclusion

Equality is a bridge between syntax and object-oriented design. Learn identity, equals, and hashCode before working deeply with sets, maps, and domain objects.

  • Java Comparison Operators
  • Java Strings
  • Java Classes and Objects
  • Java Collections Framework