Understanding the Java hashCode Method
java hashcode method: Learn how the Java hashCode method works, why it must align with equals, and how to implement it correctly for hash-based collections.
The java hashcode method is part of every Java object, but its behavior is often misunderstood until a HashMap or HashSet starts returning unexpected results. The method is defined on Object and returns an integer that represents the object's state. That integer is used by hash-based collections to determine which bucket an entry belongs to. When the method is implemented incorrectly, collections can degrade in performance or even violate their own contracts.
The hashCode Contract
The contract for hashCode is specified in the Object class. There are three rules that every implementation must satisfy:
- Consistency during execution: If an object's state does not change, calling
hashCodemultiple times must return the same integer within the same JVM run. - Equal objects must have equal hash codes: If
a.equals(b)istrue, thena.hashCode()must equalb.hashCode(). - Unequal objects may have equal hash codes: This is allowed, but collisions reduce performance. The contract does not require distinct hash codes for distinct objects.
These rules are not optional. Violating the second rule breaks the fundamental behavior of hash-based collections. If two equal objects produce different hash codes, a HashMap may store them in different buckets, and get will not find the value you put in.
The Relationship Between equals and hashCode
The equals method and the hashCode method are tightly coupled. Whenever you override equals, you must override hashCode as well. The reason is that hash-based collections first compare hash codes to narrow the search, then use equals to confirm equality. If two objects are equal but have different hash codes, the collection will never call equals on them because they are placed in different buckets.
Consider a simple Person class with a name field. If you override equals to compare names but leave hashCode as the default identity-based implementation, two Person instances with the same name will not be equal according to the collection's logic. The default hashCode returns a value derived from the object's memory address, so the two instances will almost certainly have different hash codes.
Implementing hashCode Correctly
A common and effective approach is to compute a hash code from the fields that participate in the equals comparison. The algorithm typically uses a prime number as a multiplier and accumulates the hash codes of individual fields. The number 31 is often used because it is an odd prime and produces a reasonable distribution.
Here is a typical implementation for a class with a String field and an int field:
public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Person)) return false; Person other = (Person) obj; return age == other.age && name.equals(other.name); } @Override public int hashCode() { int result = 17; result = 31 * result + name.hashCode(); result = 31 * result + age; return result; } }
The initial value 17 and the multiplier 31 are arbitrary but conventional. The important part is that the same fields are used in both equals and hashCode. If you change the fields used in equals, you must change them in hashCode too.
Using Objects.hash for a Concise Implementation
Java 7 introduced the Objects utility class with a static hash method that simplifies the implementation. It accepts a varargs array of fields and computes a combined hash code. The result is equivalent to what you would write manually, but with less boilerplate.
import java.util.Objects; public class Person { private final String name; private final int age; // constructor, equals... @Override public int hashCode() { return Objects.hash(name, age); } }
Objects.hash internally calls Arrays.hashCode on the varargs array, which handles primitive types and null values safely. This is a good default choice for most classes. However, note that it creates an array for each call, which adds a small allocation overhead. For most applications this is negligible, but if you are creating millions of objects per second, a manual implementation might be slightly faster.
Common Mistakes That Break Collections
One of the most common mistakes is using a mutable field in the hash code calculation. If an object's hash code changes after it has been inserted into a HashMap, the object will be lost. The collection stores it in the bucket corresponding to the original hash code, but on lookup the new hash code points to a different bucket.
public class MutableKey { private String value; public void setValue(String value) { this.value = value; } @Override public int hashCode() { return value.hashCode(); } }
If you mutate value after inserting this object into a HashSet, the set will be unable to find it. The solution is to make hash-code fields final or to ensure they are not modified after the object is placed in a collection.
Another mistake is forgetting to override hashCode when overriding equals. This is so common that many IDEs generate both methods together. The compiler does not enforce the relationship, so the error only surfaces at runtime.
A third mistake is using a hash code that is always the same constant. This satisfies the contract but makes every lookup a linear scan, destroying the performance benefit of hash-based collections. The hash function should distribute values reasonably across the integer range.
Performance Considerations for hashCode
Hash code computation happens on every put and get operation in a hash-based collection. If the method is expensive, it becomes a bottleneck. The cost depends on the fields involved. A String field's hashCode is cached after the first call, so repeated calls are cheap. An array field, however, computes a hash code by iterating over its elements each time, which can be costly for large arrays.
If an object is immutable and its hash code is frequently needed, you can cache the hash code in a field. This is common for immutable value objects. The hash code is computed once on first access and stored for later use.
public class Point { private final int x; private final int y; private int cachedHash; public Point(int x, int y) { this.x = x; this.y = y; } @Override public int hashCode() { if (cachedHash == 0) { cachedHash = Objects.hash(x, y); } return cachedHash; } }
Note that using 0 as a sentinel is safe only if the actual hash code can never be 0. In practice, the probability is low but not zero. If you want to be rigorous, use a separate boolean flag to indicate whether the hash has been computed.
When Not to Override hashCode
If a class is never used in hash-based collections, overriding hashCode is not strictly necessary. However, it is still good practice to override it whenever you override equals, because you cannot predict how the class will be used in the future. A library class that is part of a public API should always have a consistent equals and hashCode pair.
There is also a case where you should not override equals at all, and therefore you should not override hashCode either. This is when you want identity semantics, such as for mutable objects used as keys in a WeakHashMap or when you explicitly rely on reference equality. In those situations, the default Object implementations are correct.
For value objects like String, Integer, or custom DTOs, overriding both methods is almost always the right choice. The key is to keep the fields used in hashCode consistent with those used in equals, and to ensure those fields are stable after the object is created.