Understanding Java hashCode() and equals(): Design Principles and Practical Examples
This article explains the rules and design principles behind Java's hashCode() and equals() methods, illustrates their implementations in String, and provides concrete Person class examples showing how proper overriding affects Set and Map behavior.
Background: When reading the Alibaba Java Development Manual, the usage rules for hashCode() and equals() are highlighted: overriding equals requires overriding hashCode ; Set and Map rely on these methods to determine object uniqueness.
String's hashCode() implementation is shown, demonstrating caching of the computed hash and use of the prime number 31 for multiplication, which offers good distribution and low overflow risk.
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}Key points from the implementation include the importance of caching, lazy computation, and the choice of 31 as a prime that can be optimized by the JVM (e.g., i*31 == (i<<5)-1 ), while keeping the multiplier small enough to reduce overflow.
String's equals() implementation is also presented, illustrating reference checks, type checks, length comparison, and character‑by‑character comparison.
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}Example classes ( Person ) and test code demonstrate that overriding only equals or only hashCode leads to unexpected behavior in HashSet , while overriding both correctly results in proper deduplication.
public class Person {
private String name;
private int age;
// constructors, getters, setters omitted for brevity
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof Person) {
Person p = (Person) o;
return this.age == p.getAge() && this.name.equals(p.getName());
}
return false;
}
@Override
public int hashCode() {
return name.hashCode() * 31 + age;
}
}
public class Application {
public static void main(String[] args) {
Set
set = new HashSet<>();
Person p1 = new Person("Lilei", 25);
Person p2 = new Person("Lilei", 25);
set.add(p1);
System.out.println("set contains p1: " + set.contains(p1));
System.out.println("set contains p2: " + set.contains(p2));
}
}Design principles for equals() :
Symmetry: if x.equals(y) is true, then y.equals(x) must be true.
Reflexivity: x.equals(x) must be true.
Transitivity: if x.equals(y) and y.equals(z) are true, then x.equals(z) must be true.
Consistency: repeated calls return the same result as long as the objects remain unchanged.
Non‑nullity: x.equals(null) must return false, and comparisons with different types must return false.
Design principles for hashCode() :
The hash code must remain consistent during an object's lifetime if the information used in equals does not change.
Equal objects must produce identical hash codes.
Unequal objects are not required to have distinct hash codes, but a good distribution improves hash‑table performance.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.