For working professionals
For fresh graduates
More
6. JDK in Java
7. C++ Vs Java
16. Java If-else
18. Loops in Java
20. For Loop in Java
46. Packages in Java
53. Java Collection
56. Generics In Java
57. Java Interfaces
60. Streams in Java
63. Thread in Java
67. Deadlock in Java
74. Applet in Java
75. Java Swing
76. Java Frameworks
78. JUnit Testing
81. Jar file in Java
82. Java Clean Code
86. Java 8 features
87. String in Java
93. HashMap in Java
98. Enum in Java
101. Hashcode in Java
105. Linked List in Java
109. Array Length in Java
111. Split in java
112. Map In Java
115. HashSet in Java
118. DateFormat in Java
121. Java List Size
122. Java APIs
128. Identifiers in Java
130. Set in Java
132. Try Catch in Java
133. Bubble Sort in Java
135. Queue in Java
142. Jagged Array in Java
144. Java String Format
145. Replace in Java
146. charAt() in Java
147. CompareTo in Java
151. parseInt in Java
153. Abstraction in Java
154. String Input in Java
156. instanceof in Java
157. Math Floor in Java
158. Selection Sort Java
159. int to char in Java
164. Deque in Java
172. Trim in Java
173. RxJava
174. Recursion in Java
175. HashSet Java
177. Square Root in Java
190. Javafx
Proper implementation of the equals() and hashCode() methods is critical for creating Java applications, especially when working with collections.The equals and hashcode in Java are fundamental methods from the Object class that every Java developer must understand.In this guide, we'll explore the equals and hashcode contract in Java Programming, examine proper implementation techniques, and discuss common pitfalls to avoid when overriding these methods.
Looking to go pro in Java? upGrad’s software engineering course offers hands-on training and expert guidance.
Before diving into the contract between equals and hashcode, let's understand what these methods do individually.
The equals() method in Java is used to compare the equality of two objects. By default, the equals() method provided by the Object class compares object references, essentially checking if two references point to the same memory location.
Here's the default implementation in the Object class:
public boolean equals(Object obj) {
return (this == obj);
}
This default implementation is often insufficient for custom classes where logical equality matters more than reference equality. For example, two Person objects with the same name and age might be considered equal even if they are different objects in memory.
The hashCode() method returns an integer value that represents the object in hash-based collections like HashMap, HashSet, and Hashtable. The default implementation in the Object class typically returns a memory address-based hash value.
Here's how you might think of the default hashCode() method:
public int hashCode() {
// Implementation depends on JVM, but typically involves memory address
return /* some computation based on object's memory address */;
}
Become a versatile developer with these professional programs: • Full Stack Development Bootcamp – upGrad • AI Full Stack Developer Course – IIIT Bangalore • Certificate in Cloud & DevOps – upGrad
The equals and hashcode contract in Java consists of specific rules that must be followed when overriding these methods. This contract ensures hash-based collections work correctly with your custom classes.
Breaking the equals and hashcode contract in Java can lead to unpredictable behavior, especially with hash-based collections:
Let's see a concrete example of what happens when we break the contract:
// Breaking the equals-hashCode contract
public class ContractBreaker {
private String value;
public ContractBreaker(String value) {
this.value = value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ContractBreaker other = (ContractBreaker) obj;
return value.equals(other.value);
}
// We're NOT overriding hashCode() - breaking the contract!
public static void main(String[] args) {
Set<ContractBreaker> set = new HashSet<>();
ContractBreaker cb1 = new ContractBreaker("test");
ContractBreaker cb2 = new ContractBreaker("test");
// These objects are equal according to equals()
System.out.println("cb1.equals(cb2): " + cb1.equals(cb2));
// But they have different hash codes
System.out.println("cb1.hashCode(): " + cb1.hashCode());
System.out.println("cb2.hashCode(): " + cb2.hashCode());
// Adding both to a HashSet
set.add(cb1);
set.add(cb2);
// HashSet should contain only unique elements
// But since we broke the contract, both objects are added
System.out.println("Set size: " + set.size());
}
}
Expected Output:
cb1.equals(cb2): true
cb1.hashCode(): 123456789 // Actual values will differ
cb2.hashCode(): 987654321 // Actual values will differ
Set size: 2
Explanation: In this example, we broke the contract by overriding equals() but not hashCode(). Even though cb1 and cb2 are equal according to our equals() implementation, they have different hash codes. As a result, HashSet treats them as different objects and stores both, violating the uniqueness guarantee of the set.
Let's look at the proper way to implement equals and hashcode in Java.
A correct implementation of equals() should follow these principles:
Here's a proper implementation:
@Override
public boolean equals(Object obj) {
// Check if the compared object is the same instance
if (this == obj) return true;
// Check if the compared object is null or different class
if (obj == null || getClass() != obj.getClass()) return false;
// Cast to the specific class
Person person = (Person) obj;
// Compare the relevant fields
return age == person.age &&
(name == null ? person.name == null : name.equals(person.name));
}
A proper hashCode() implementation should:
Here's a good implementation:
@Override
public int hashCode() {
// Use the same fields as in equals()
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + (name == null ? 0 : name.hashCode());
return result;
}
public class Person {
private String name;
private 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 == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
public static void main(String[] args) {
Person person1 = new Person("John", 30);
Person person2 = new Person("John", 30);
Person person3 = new Person("Jane", 25);
// Testing equals()
System.out.println("person1.equals(person2): " + person1.equals(person2));
System.out.println("person1.equals(person3): " + person1.equals(person3));
// Testing hashCode()
System.out.println("person1.hashCode(): " + person1.hashCode());
System.out.println("person2.hashCode(): " + person2.hashCode());
System.out.println("person3.hashCode(): " + person3.hashCode());
// Testing with HashSet
Set<Person> persons = new HashSet<>();
persons.add(person1);
persons.add(person2);
persons.add(person3);
System.out.println("HashSet size: " + persons.size());
}
}
Expected Output:
person1.equals(person2): true
person1.equals(person3): false
person1.hashCode(): 2142223
person2.hashCode(): 2142223
person3.hashCode(): 2038272
HashSet size: 2
Explanation: In this example, person1 and person2 are considered equal because they have the same name and age. As required by the hashcode and equals contract in Java, they also have the same hash code. When added to a HashSet, only one instance is stored due to the equality. person3 has different field values, resulting in a different hash code, and is stored as a separate entry in the HashSet.
Let's explore some best practices for implementing the hashcode method in Java:
Any field used in the equals() method should also be used in the hashCode() method. This ensures consistency between the two methods and maintains the contract.
Using prime numbers in hash code calculations helps distribute the hash codes more uniformly:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + field1;
result = prime * result + (field2 == null ? 0 : field2.hashCode());
// Add more fields as needed
return result;
}
If your class contains arrays, use Arrays.hashCode() or Arrays.deepHashCode() for nested arrays:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(arrayField);
return result;
}
Java 7 introduced Objects.hash() as a convenient way to compute hash codes:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
For immutable classes, consider caching the hash code to improve performance:
public final class ImmutablePerson {
private final String name;
private final int age;
private final int hashCode;
public ImmutablePerson(String name, int age) {
this.name = name;
this.age = age;
this.hashCode = computeHashCode();
}
private int computeHashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + (name == null ? 0 : name.hashCode());
return result;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ImmutablePerson person = (ImmutablePerson) obj;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
}
Inheritance complicates the implementation of equals() and hashCode(). Here are some considerations:
Let's see an example of the challenges with inheritance:
class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Shape shape = (Shape) obj;
return color.equals(shape.color);
}
@Override
public int hashCode() {
return color.hashCode();
}
}
class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) return false;
// obj must be a Circle at this point, so safe to cast
Circle circle = (Circle) obj;
return Double.compare(circle.radius, radius) == 0;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + Double.hashCode(radius);
}
}
Hash-based collections rely heavily on proper equals() and hashCode() implementations:
public class StudentRepository {
private Map<Student, List<Course>> enrollments = new HashMap<>();
public void enroll(Student student, Course course) {
enrollments.computeIfAbsent(student, k -> new ArrayList<>()).add(course);
}
public List<Course> getCoursesForStudent(Student student) {
return enrollments.getOrDefault(student, Collections.emptyList());
}
public static void main(String[] args) {
StudentRepository repository = new StudentRepository();
// Create a student and enroll in courses
Student student1 = new Student("S123", "Alice");
repository.enroll(student1, new Course("CS101"));
// Create an "equal" student object with the same ID
Student student2 = new Student("S123", "Alice");
// Retrieve courses for student2 - this works correctly only if
// equals() and hashCode() are properly implemented
List<Course> courses = repository.getCoursesForStudent(student2);
System.out.println("Courses: " + courses.size());
}
}
class Student {
private String id;
private String name;
// Constructor, getters, and setters...
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return id.equals(student.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
}
class Course {
private String code;
// Constructor, getters, and setters...
// Proper equals() and hashCode() implementations...
}
Expected Output:
Courses: 1
Explanation: Because equals() and hashCode() are properly implemented based on the student ID, student2 is considered equal to student1. This allows student2 to be used as a key to retrieve the courses that were enrolled using student1.
This is the most common violation of the hashcode and equals method in Java contract:
// INCORRECT: Overriding equals() without hashCode()
@Override
public boolean equals(Object obj) {
// Implementation...
}
// No hashCode() override!
Using mutable fields in hashCode() can cause objects to "disappear" in collections:
// PROBLEMATIC: Using mutable field for hash code
public class MutablePerson {
private String name; // Mutable
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return name.hashCode(); // Based on mutable field
}
// equals() implementation...
}
Violating the symmetry or transitivity properties:
// INCORRECT: Asymmetric equals()
public class AsymmetricEquals {
private String value;
@Override
public boolean equals(Object obj) {
if (obj instanceof String) {
return value.equals(obj); // Compares with String
}
if (obj instanceof AsymmetricEquals) {
return value.equals(((AsymmetricEquals) obj).value);
}
return false;
}
// hashCode() implementation...
}
Creating unnecessary objects or performing expensive operations:
// INEFFICIENT: Creating temporary objects
@Override
public int hashCode() {
return new StringBuilder(firstName).append(lastName).toString().hashCode();
}
If a field is important enough to check in equals(), it should be included in hashCode():
// INCORRECT: Missing fields in hashCode()
public class IncompleteHash {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
// Checks both name and age
// ...
}
@Override
public int hashCode() {
return name.hashCode(); // Only uses name, ignores age
}
}
Modern IDEs can generate these methods for you:
With Lombok, you can use annotations:
import lombok.EqualsAndHashCode;
@EqualsAndHashCode
public class Person {
private String name;
private int age;
// No need to manually implement equals() and hashCode()
}
Properly implementing equals and hashcode in Java is essential for creating robust applications, particularly when working with collections.
By understanding and following the equals and hashcode contract, you can ensure your objects behave predictably in all contexts.
Remember that both methods must be consistent with each other and based on the same fields. When in doubt, use modern tools like IDE generators or libraries like Lombok to help create correct implementations.
The key takeaways from this guide:
By following these guidelines, you'll avoid common pitfalls and ensure your Java applications work correctly with collections and comparison operations.
The equals() method is used to compare two objects for equality. It determines whether one object is considered equal to another, based on the specific criteria defined in the method's implementation. While the default implementation compares object references, custom implementations typically compare object content.
The relationship is defined by the contract: if two objects are equal according to the equals() method, they must have the same hash code. This contract ensures that hash-based collections like HashMap and HashSet work correctly. However, two objects with the same hash code are not necessarily equal.
You should override both methods to maintain the contract between them. If you override only equals(), two equal objects might have different hash codes (breaking the contract). If you override only hashCode(), objects with the same hash code might not be equal. Both scenarios can cause unexpected behavior with hash-based collections.
The hashCode() method determines which bucket in a HashMap an object goes into. A good hash function distributes objects evenly across buckets, resulting in O(1) average access time. Poor hash functions can lead to many objects in the same bucket, degrading performance to O(n) in the worst case.
Yes, this is called a hash collision. Since the hash code is an integer and there are more possible objects than integer values, collisions are inevitable. That's why hash-based collections also use the equals() method to distinguish between objects with the same hash code.
If you change fields used in the hashCode() calculation after adding an object to a hash-based collection, the object might "disappear" in the collection. This is because the collection will look for it in a different bucket based on its new hash code.
No, random numbers violate the contract requiring consistency. A hash code should be deterministic and based on the object's state that's relevant to equality. Random numbers would cause the same object to have different hash codes across different invocations.
While technically this doesn't violate the contract, it defeats the purpose of having hash codes. All objects would be placed in the same bucket in hash-based collections, reducing their performance to that of a linked list (O(n) instead of O(1)).
In equals(), use the null-safe comparison: (field == null ? other.field == null : field.equals(other.field)). In hashCode(), use a default value for null fields: (field == null ? 0 : field.hashCode()). Alternatively, use Objects.equals() and Objects.hash() methods which handle null values properly.
Use Double.compare() or Float.compare() methods instead of the == operator to account for special values like NaN and to handle precision issues.
While technically possible, it's generally not recommended as it can violate the symmetry requirement of the contract. If a.equals(b) returns true, then b.equals(a) should also return true, which is difficult to ensure across different classes.
The == operator compares object references (checks if two references point to the same object in memory). The equals() method, when properly overridden, compares the logical equality of objects based on their content or business identity.
Take the Free Quiz on Java
Answer quick questions and assess your Java knowledge
Talk to our experts. We are available 7 days a week, 9 AM to 12 AM (midnight)
Indian Nationals
1800 210 2020
Foreign Nationals
+918068792934
1.The above statistics depend on various factors and individual results may vary. Past performance is no guarantee of future results.
2.The student assumes full responsibility for all expenses associated with visas, travel, & related costs. upGrad does not provide any a.