Java Equality: Understanding == vs equals()
java equality: Learn how Java equality works: the difference between == and equals(), overriding equals and hashCode, and common pitfalls.
In Java, equality is not a single concept. The == operator and the equals() method answer different questions, and mixing them up produces bugs that are hard to trace. == compares references, not object contents. equals() is a method that can be overridden to define logical equality. Understanding this distinction is the core of java equality.
The Difference Between == and equals()
The == operator performs reference comparison for objects. It checks whether two variables point to the same memory address. For primitive types, == compares values, but for objects, it never compares fields.
String a = new String("hello"); String b = new String("hello"); System.out.println(a == b); // false System.out.println(a.equals(b)); // true
Here, a and b are two distinct objects with identical content. == returns false because they are different references. equals() returns true because the String class overrides it to compare character sequences.
How equals() Works in Object and Why Override
The default implementation of equals() in java.lang.Object uses ==. Without overriding, two objects are equal only if they are the same instance. This is rarely what you want for value objects like Person, Money, or Point.
Consider a simple Person class:
public class Person { private String name; private int age; // constructor, getters @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && name.equals(person.name); } }
The override checks identity first, then type, then compares each field. Without this, two Person instances with the same data would not be equal, breaking logic that relies on value comparison.
The hashCode() Contract with equals()
When you override equals(), you must also override hashCode(). The contract states that equal objects must have equal hash codes. If you violate this, collections like HashSet and HashMap will misbehave.
@Override public int hashCode() { return Objects.hash(name, age); }
Using Objects.hash() is a concise way to generate a hash based on the same fields used in equals(). The critical rule: if a.equals(b) is true, then a.hashCode() == b.hashCode() must also be true. The reverse is not required—unequal objects may share a hash code.
Common Mistakes When Implementing equals()
The most frequent error is using getClass() when instanceof would be more appropriate, or vice versa. getClass() enforces exact type equality, which breaks subclass equality. For example, if Employee extends Person, an Employee and a Person with the same fields will not be equal under getClass(). Using instanceof allows subclass instances to be equal if their fields match, but it can violate symmetry if the subclass overrides equals() inconsistently.
Another mistake is forgetting to handle null in field comparisons. Calling name.equals(other.name) throws NullPointerException if name is null. Use Objects.equals(name, other.name) to handle null safely.
Using Objects.equals() and Java 7+ Utilities
Java 7 introduced java.util.Objects, which provides null-safe equality checks. This simplifies equals() implementations and avoids repetitive null checks.
@Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && Objects.equals(name, person.name); }
Objects.equals(a, b) returns true if both are null, false if one is null, and otherwise delegates to a.equals(b). This is a small but meaningful improvement in maintainability.
Equality in Collections: HashSet and HashMap Behavior
Collections that rely on hashing use both hashCode() and equals(). When you add an object to a HashSet, the set computes its hash to find the bucket, then uses equals() to check for duplicates within that bucket. If equals() is overridden but hashCode() is not, two equal objects may land in different buckets, allowing duplicates.
Set<Person> people = new HashSet<>(); people.add(new Person("Alice", 30)); people.add(new Person("Alice", 30)); System.out.println(people.size()); // 1 if equals and hashCode are correct
If you only override equals() and not hashCode(), the size will be 2 because the two objects produce different hash codes. This is a classic source of subtle bugs.
Performance and Maintainability Considerations
Overriding equals() and hashCode() correctly has a direct impact on collection performance. A poor hashCode() that returns a constant, for example, degrades HashMap to a linked list, turning O(1) lookups into O(n). Use meaningful fields and avoid including mutable fields in the hash computation. If an object's hash changes after it is placed in a hash-based collection, the collection will lose track of it.
From a maintainability standpoint, keep equals() and hashCode() consistent with each other and with the natural identity of the object. If you add a field to the class, decide whether it participates in equality. Changing equals() semantics after objects have been stored in collections can cause data loss.
When to Use == vs equals()
Use == when you explicitly need reference identity, such as checking for null or comparing enum constants. For value comparison, always use equals(). In practice, == on objects is almost always a mistake unless you are implementing low-level identity semantics.
For enums, == is safe because each enum constant is a singleton. For strings, never use == unless you are certain the references are interned, which is rarely the case. The general rule: if you care about the content, use equals(); if you care about the identity, use ==.
A practical decision criterion: if two objects should be interchangeable in your application logic, they should be equal via equals(). If they are merely the same instance, == is appropriate. This distinction is the heart of java equality, and getting it right prevents a wide class of defects.