Java equals() and hashCode() in Collections
Java equals() and hashCode()
Two methods on Object. Both inherited by every Java class. Both quietly broken in every class that never overrides them — and that brokenness only shows up at runtime, in the form of HashMap lookups that return null for keys that look identical, or HashSet that silently stores what should be duplicates.
Understanding equals() and hashCode() means understanding the contract between them, why that contract exists, and what breaks when it is violated.
What Are equals() and hashCode()?
Every Java class inherits two methods from java.lang.Object:
equals(Object obj) determines whether two object references point to logically equivalent objects. By default — when not overridden — it uses ==: identity comparison. Two different new Product("LAPTOP-001") objects are not equal by default even though they represent the same product.
hashCode() returns an int that represents the object for hashing purposes. By default, it is based on the object's memory address — a value that varies between JVM runs and has nothing to do with the object's logical content.
The diagram below shows what happens with and without overriding these methods.
WITHOUT OVERRIDE (inherits Object behaviour):
Product p1 = new Product("LAPTOP-001", 45999.0);
Product p2 = new Product("LAPTOP-001", 45999.0);
p1.equals(p2) → false (different memory addresses)
p1 == p2 → false (different references)
p1.hashCode() → 1829164700 (some address-based number)
p2.hashCode() → 1347067481 (a different address-based number)
WITH OVERRIDE (based on SKU field):
p1.equals(p2) → true (same SKU value)
p1.hashCode() → same (same SKU produces same hash)
HashMap result WITHOUT override:
map.put(p1, "in stock");
map.get(p2) → null (p2 seen as a different key — WRONG)
HashMap result WITH override:
map.put(p1, "in stock");
map.get(p2) → "in stock" (p2 recognized as same key — CORRECT)
This is the core problem both methods exist to solve: giving hash-based collections a way to recognise logically equal objects regardless of their memory location.
The Contract Between equals() and hashCode()
The Java specification defines a contract that any correct implementation must satisfy. Violating it breaks HashMap, HashSet, and every other hash-based collection silently — no exception is thrown, the data simply disappears or duplicates where it should not.
THE CONTRACT (from java.lang.Object documentation): Rule 1 — CONSISTENCY WITHIN A RUN: Multiple calls to hashCode() on the same object must return the same integer, as long as the fields used in equals() are not modified. Rule 2 — EQUAL OBJECTS MUST HAVE EQUAL HASH CODES: if a.equals(b) is true, then a.hashCode() == b.hashCode() MUST be true. This is the critical rule — breaking it makes HashMap lose entries. Rule 3 — UNEQUAL OBJECTS NEED NOT HAVE DIFFERENT HASH CODES: if a.hashCode() == b.hashCode(), a.equals(b) MAY be false. Collisions (same hash, different object) are allowed and handled. But many collisions degrade HashMap from O(1) to O(n). Rule 4 — SYMMETRY, REFLEXIVITY, TRANSITIVITY of equals(): a.equals(a) must be true (reflexive) a.equals(b) == b.equals(a) must be true (symmetric) a.equals(b) && b.equals(c) implies a.equals(c) (transitive) a.equals(null) must be false (null-safe)
Rule 2 is the one that causes real bugs. It says: if two objects are considered equal, they MUST produce the same hash code. The reverse — equal hash codes imply equal objects — is NOT required.
How HashMap Uses Both Methods
The internal working of HashMap makes the contract concrete. Every put() and get() calls both methods.
HashMap<Product, String> catalogue = new HashMap<>();
catalogue.put(product, "in stock");
catalogue.get(anotherProduct); ← how does this work?
STEP 1: Compute hash
int h = anotherProduct.hashCode();
int hash = h ^ (h >>> 16); ← spread high bits into low bits
STEP 2: Find bucket
int index = (capacity - 1) & hash; ← typically 0..15 for default size
STEP 3: Search bucket chain
For each Node in table[index]:
if (node.hash == hash && node.key.equals(anotherProduct)):
return node.value; ← found!
return null; ← not found
The two-step process:
hashCode() narrows the search to one bucket → fast (O(1))
equals() finds the exact match in the chain → precise
If hashCode() is overridden but equals() is not, two logically equal objects land in the same bucket but equals() returns false (default identity check), so no match is found. If equals() is overridden but hashCode() is not, two equal objects may land in completely different buckets and the get() returns null.
Basic Overview — equals() from Object
The default equals() and what overriding it means, starting from the simplest case.
// File: EqualsBasicsDemo.java
import java.util.Objects;
public class EqualsBasicsDemo {
// Simple value class — two products are equal if their SKU matches
static class Product {
private final String sku;
private final String name;
private final double price;
Product(String sku, String name, double price) {
this.sku = sku;
this.name = name;
this.price = price;
}
// Without this override, equals() uses identity (==)
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // same reference — trivially equal
if (obj == null) return false; // null is never equal to an object
if (!(obj instanceof Product other)) return false; // different class
return Objects.equals(this.sku, other.sku); // compare by SKU only
}
@Override
public String toString() { return "Product[" + sku + "]"; }
}
public static void main(String[] args) {
Product p1 = new Product("LAPTOP-001", "Lenovo ThinkPad", 45999.0);
Product p2 = new Product("LAPTOP-001", "Lenovo ThinkPad", 45999.0);
Product p3 = new Product("MOUSE-007", "Logitech MX", 1299.0);
System.out.println("=== Reference comparison (==) ===");
System.out.println("p1 == p2 : " + (p1 == p2)); // always false — different objects
System.out.println("p1 == p1 : " + (p1 == p1)); // always true — same reference
System.out.println("\n=== Logical comparison (equals) ===");
System.out.println("p1.equals(p2) : " + p1.equals(p2)); // true — same SKU
System.out.println("p1.equals(p3) : " + p1.equals(p3)); // false — different SKU
System.out.println("p1.equals(null): " + p1.equals(null)); // false — null safe
System.out.println("\n=== Symmetry check ===");
System.out.println("p1.equals(p2) : " + p1.equals(p2)); // true
System.out.println("p2.equals(p1) : " + p2.equals(p1)); // true — must match
}
}Output:
=== Reference comparison (==) ===
p1 == p2 : false
p1 == p1 : true
=== Logical comparison (equals) ===
p1.equals(p2) : true
p1.equals(p3) : false
p1.equals(null): false
=== Symmetry check ===
p1.equals(p2) : true
p2.equals(p1) : true
Implementing hashCode() Correctly
hashCode() must be implemented alongside equals() — they form a pair. The standard modern approach uses Objects.hash(), which handles null fields safely and applies a good hash mix automatically.
// File: HashCodeImplementationDemo.java
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class HashCodeImplementationDemo {
static class UserAccount {
private final String userId;
private final String email;
private final String role;
UserAccount(String userId, String email, String role) {
this.userId = userId;
this.email = email;
this.role = role;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof UserAccount other)) return false;
// Two accounts are equal if userId AND email both match
return Objects.equals(this.userId, other.userId)
&& Objects.equals(this.email, other.email);
}
@Override
public int hashCode() {
// Objects.hash() combines fields using a prime-based mix
// Both fields used in equals() must appear here — no more, no less
return Objects.hash(userId, email);
}
@Override
public String toString() {
return "User[" + userId + ", " + email + ", " + role + "]";
}
}
public static void main(String[] args) {
UserAccount acc1 = new UserAccount("U001", "priya@zepto.in", "ADMIN");
UserAccount acc2 = new UserAccount("U001", "priya@zepto.in", "ADMIN"); // same logical user
UserAccount acc3 = new UserAccount("U002", "rohan@zepto.in", "USER");
System.out.println("=== hashCode values ===");
System.out.println("acc1.hashCode() : " + acc1.hashCode());
System.out.println("acc2.hashCode() : " + acc2.hashCode()); // must equal acc1's hash
System.out.println("acc3.hashCode() : " + acc3.hashCode()); // different — different user
System.out.println("\n=== HashSet — deduplication ===");
Set<UserAccount> activeUsers = new HashSet<>();
activeUsers.add(acc1);
activeUsers.add(acc2); // duplicate — add() returns false, set unchanged
activeUsers.add(acc3);
System.out.println("Set size: " + activeUsers.size()); // 2, not 3
activeUsers.forEach(u -> System.out.println(" " + u));
System.out.println("\n=== HashMap — key lookup ===");
Map<UserAccount, String> sessionMap = new HashMap<>();
sessionMap.put(acc1, "SESSION-TOKEN-XYZ");
// acc2 is a different object but equal to acc1 — must find the token
String token = sessionMap.get(acc2);
System.out.println("get(acc2) token : " + token); // SESSION-TOKEN-XYZ — not null
System.out.println("get(acc3) token : " + sessionMap.get(acc3)); // null — different user
}
}Output:
=== hashCode values ===
acc1.hashCode() : 1073314563
acc2.hashCode() : 1073314563
acc3.hashCode() : 1073346271
=== HashSet — deduplication ===
Set size: 2
User[U001, priya@zepto.in, ADMIN]
User[U002, rohan@zepto.in, USER]
=== HashMap — key lookup ===
get(acc2) token : SESSION-TOKEN-XYZ
get(acc3) token : null
Three Ways to Implement equals() and hashCode()
Option 1 — Manual Implementation
Full control, most verbose. Useful when fine-grained control over comparison or performance is needed.
// File: ManualImplementation.java
import java.util.Objects;
public class ManualImplementation {
static class OrderItem {
private final String productId;
private final int quantity;
private final double unitPrice;
OrderItem(String productId, int quantity, double unitPrice) {
this.productId = productId;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
OrderItem other = (OrderItem) obj;
return this.quantity == other.quantity
&& Double.compare(this.unitPrice, other.unitPrice) == 0
&& Objects.equals(this.productId, other.productId);
}
// Manual prime-based implementation — what IDE generators produce
@Override
public int hashCode() {
int result = Objects.hashCode(productId);
result = 31 * result + quantity;
result = 31 * result + Double.hashCode(unitPrice);
return result;
}
}
}Option 2 — Objects.hash() (Recommended)
Cleaner, null-safe, and harder to get wrong. Use this in most production code.
// File: ObjectsHashDemo.java
import java.util.Objects;
public class ObjectsHashDemo {
static class DeliveryAddress {
private final String street;
private final String city;
private final String pincode;
DeliveryAddress(String street, String city, String pincode) {
this.street = street;
this.city = city;
this.pincode = pincode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof DeliveryAddress other)) return false;
return Objects.equals(street, other.street)
&& Objects.equals(city, other.city)
&& Objects.equals(pincode,other.pincode);
}
@Override
public int hashCode() {
// Single call — all fields combined, null-safe
return Objects.hash(street, city, pincode);
}
}
public static void main(String[] args) {
DeliveryAddress addr1 = new DeliveryAddress("42 MG Road", "Bengaluru", "560001");
DeliveryAddress addr2 = new DeliveryAddress("42 MG Road", "Bengaluru", "560001");
DeliveryAddress addr3 = new DeliveryAddress("15 Linking Rd", "Mumbai", "400054");
System.out.println("addr1.equals(addr2) : " + addr1.equals(addr2)); // true
System.out.println("addr1.hashCode() == addr2.hashCode(): "
+ (addr1.hashCode() == addr2.hashCode())); // true
System.out.println("addr1.equals(addr3) : " + addr1.equals(addr3)); // false
}
}Output:
addr1.equals(addr2) : true
addr1.hashCode() == addr2.hashCode(): true
addr1.equals(addr3) : false
Option 3 — Java Records (Java 16+)
Records auto-generate correct equals(), hashCode(), and toString() from all components. For immutable value classes, this is the cleanest approach.
// File: RecordEqualsDemo.java
import java.util.HashSet;
import java.util.Set;
public class RecordEqualsDemo {
// record generates equals, hashCode, toString automatically from components
record Coupon(String code, int discountPercent) {}
public static void main(String[] args) {
Coupon c1 = new Coupon("SAVE20", 20);
Coupon c2 = new Coupon("SAVE20", 20);
Coupon c3 = new Coupon("FLAT50", 50);
System.out.println("c1.equals(c2) : " + c1.equals(c2)); // true — auto-generated
System.out.println("c1.equals(c3) : " + c1.equals(c3)); // false
Set<Coupon> appliedCoupons = new HashSet<>();
appliedCoupons.add(c1);
appliedCoupons.add(c2); // duplicate — rejected by HashSet
appliedCoupons.add(c3);
System.out.println("Applied coupons count: " + appliedCoupons.size()); // 2
}
}Output:
c1.equals(c2) : true
c1.equals(c3) : false
Applied coupons count: 2
Basic Example
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Point p1 = new Point(2, 3);
Point p2 = new Point(2, 3); // same coordinates, different object
System.out.println("p1 == p2 : " + (p1 == p2));
System.out.println("p1.equals(p2) : " + p1.equals(p2));
Set<Point> points = new HashSet<>();
points.add(p1);
points.add(p2); // logically the same point — rejected as a duplicate
System.out.println("Set size: " + points.size());
}
}
class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Point other)) return false;
return x == other.x && y == other.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}Output:
p1 == p2 : false
p1.equals(p2) : true
Set size: 1
How it works:
- ›
p1andp2are two separate objects in memory, sop1 == p2isfalse— reference equality only checks identity. - ›
equals()is overridden to compare thexandyfields instead, sop1.equals(p2)correctly reportstruebecause they represent the same coordinates. - ›
hashCode()is overridden alongsideequals()usingObjects.hash(x, y), sop1andp2produce the same hash code — this is required forHashSetto recognize them as duplicates. - ›Because both methods are overridden consistently, adding both points to a
HashSetkeeps the size at1instead of2— withouthashCode(), the set would treat them as different entries even thoughequals()says they match.
Performance Considerations
A poor hashCode() implementation degrades HashMap from O(1) to O(n). The examples below show the spectrum from worst to best.
HASH CODE QUALITY — IMPACT ON HASHMAP:
CONSTANT hash (absolute worst):
@Override public int hashCode() { return 42; }
Every entry lands in bucket 42 — HashMap becomes a linked list
get() degrades from O(1) average to O(n) — fatal for large maps
PARTIAL fields (common mistake):
@Override public int hashCode() { return Objects.hash(city); }
All products from Mumbai land in the same bucket
Cities in India concentrate entries — poor distribution
ALL identity fields (correct):
@Override public int hashCode() { return Objects.hash(productId, sku); }
Entries spread across all buckets
get() stays O(1) average — maximum performance
JAVA 8 TREEIFICATION (safety net):
When any bucket chain grows beyond 8 entries, HashMap converts
the chain to a Red-Black tree — O(log n) instead of O(n)
This catches poor hashCode() implementations but does not fix them
Memory: Each HashMap node holds hash, key, value, and next pointer — approximately 48 bytes. A poor hash function that concentrates all entries in one chain wastes this structure without gaining any lookup benefit.
Best Practices
Always override both methods together, never just one. An IDE like IntelliJ or Eclipse generates both with one action — Alt+Insert → Generate → equals() and hashCode(). Overriding only equals() breaks HashSet and HashMap silently. Overriding only hashCode() breaks equals()-based comparisons in List.contains() and Collections.frequency().
Include exactly the fields that define identity in both methods. If two Order objects with the same orderId are the same order, use only orderId in both equals() and hashCode(). Do not include mutable status fields — if the status changes after an Order is put in a HashSet, the hash code changes and the entry becomes unreachable.
Use Objects.hash() for hashCode() and Objects.equals() for field comparison in equals(). Both handle null fields correctly. Writing this.field.hashCode() throws NullPointerException if the field is null. Objects.hash(field) does not.
For value classes that will never change, use Java records. Records generate correct equals(), hashCode(), and toString() from all components automatically. They enforce immutability, which removes the mutable-key bug entirely. Any class that is logically a bundle of final values is a candidate for a record.
Common Mistakes
Mistake 1 — Overriding equals() Without hashCode()
// WRONG — equals() overridden, hashCode() left as Object's identity hash
class Employee {
String employeeId;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Employee other)) return false;
return Objects.equals(this.employeeId, other.employeeId);
}
// hashCode() NOT overridden — defaults to identity hash
}
// Consequence: two equal employees have different hash codes
Employee e1 = new Employee("E-001");
Employee e2 = new Employee("E-001");
e1.equals(e2); // true — overridden equals says equal
e1.hashCode() == e2.hashCode(); // false — different addresses!
Set<Employee> team = new HashSet<>();
team.add(e1);
team.contains(e2); // false — e2 goes to different bucket than e1
// The set silently stores both "E-001" employees as separate entriesMistake 2 — Using a Mutable Field in hashCode()
// WRONG — status is mutable; changing it after insertion loses the entry
class Task {
String taskId;
String status; // mutable — changes from OPEN to DONE
@Override public boolean equals(Object obj) { ... }
@Override
public int hashCode() {
return Objects.hash(taskId, status); // status changes after put()!
}
}
Set<Task> openTasks = new HashSet<>();
Task task = new Task("T-001", "OPEN");
openTasks.add(task);
task.status = "DONE"; // hash code changes!
openTasks.contains(task); // false — task is lost in wrong bucket
openTasks.remove(task); // false — cannot remove either
// CORRECT — hashCode uses only the immutable identity field
@Override
public int hashCode() {
return Objects.hash(taskId); // taskId never changes
}Mistake 3 — Breaking Symmetry in equals()
// WRONG — equals is not symmetric: employee.equals(manager) != manager.equals(employee)
class Employee {
String id;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Employee)) return false; // accepts Manager as well
return Objects.equals(this.id, ((Employee) obj).id);
}
}
class Manager extends Employee {
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Manager)) return false; // only accepts Manager
return Objects.equals(this.id, ((Manager) obj).id);
}
}
Employee emp = new Employee(); emp.id = "E-001";
Manager mgr = new Manager(); mgr.id = "E-001";
emp.equals(mgr); // true — Employee.equals accepts Manager
mgr.equals(emp); // false — Manager.equals rejects Employee
// Symmetry broken — this causes unpredictable List.contains() resultsMistake 4 — Comparing Floating-Point With ==
// WRONG — floating-point equality with == misses precision edge cases
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PricePoint other)) return false;
return this.amount == other.amount; // floating-point == unreliable
}
// CORRECT — use Double.compare() for doubles and BigDecimal.compareTo() for currency
@Override
public boolean equals(Object obj) {
if (!(obj instanceof PricePoint other)) return false;
return Double.compare(this.amount, other.amount) == 0;
}Interview Questions
Q1. What is the contract between equals() and hashCode() in Java?
The contract has two key rules. First, if a.equals(b) returns true, then a.hashCode() must equal b.hashCode() — no exceptions. Second, the reverse is not required: equal hash codes do not imply equal objects — this is a hash collision and is handled by the chain or tree structure inside HashMap. Violating Rule 1 is the silent killer: HashMap stores an entry using key A's bucket (based on A's hash), but retrieval for an equal key B goes to a different bucket (B's different hash) and returns null. No exception fires — the data just vanishes.
Q2. What happens if you override equals() but not hashCode()?
HashSet silently stores duplicates and HashMap loses entries. When you put(key, value), the key's hash (from Object.hashCode()) determines the bucket. When you later get(equalKey), equalKey.hashCode() returns a different number (identity-based), so it looks in a different bucket, finds nothing, and returns null. list.contains() still works because ArrayList uses equals() for a linear scan — but every hash-based collection breaks. This is why the Java specification says both must always be overridden together.
Q3. Why should Map keys be immutable?
HashMap places an entry in a bucket based on the key's hashCode() at the time of put(). If the key is a mutable object and a field used in hashCode() changes after insertion, the key's hash code changes — but the entry stays in the original bucket. Any subsequent get() with the same (now-modified) key computes a new bucket, finds nothing there, and returns null. The entry is now unreachable — effectively lost. Using String, Integer, or a record as a map key avoids this entirely because those types are immutable.
Q4. What does Objects.hash() do internally?
Objects.hash(field1, field2, ...) calls Arrays.hashCode(new Object[]{field1, field2, ...}) internally. It iterates the array, starting with result = 1, and for each element computes result = 31 * result + (element == null ? 0 : element.hashCode()). The prime 31 is chosen because it produces good distribution for typical string-like values and is fast to compute as a bit-shift operation (n << 5) - n. Null fields are mapped to 0, making the method null-safe — calling field.hashCode() directly on a null field would throw NullPointerException.
Q5. How do Java records handle equals() and hashCode()?
Records automatically generate equals(), hashCode(), and toString() from all declared components (the fields in the record header). The generated equals() checks that both objects are of the same record type and all component fields are equal. The generated hashCode() uses all component fields. Records are immutable by default — all components are private final — which eliminates the mutable-key problem entirely. For value-class use cases in modern Java, records are the cleanest and least error-prone implementation choice.
Q6. When should you NOT include a field in equals() and hashCode()?
Exclude fields that are: (1) mutable and change after object creation — changing them after the object is placed in a HashSet or used as a HashMap key corrupts the collection; (2) derived or computed from other included fields — redundant inclusion does not add correctness but increases collision risk if the computation creates similar values; (3) not part of the object's identity — for example, a createdAt timestamp on a Product is metadata, not identity. Only include fields that you would compare to decide "are these the same thing in the domain model?"
FAQs
Do I need to override equals() and hashCode() for every Java class?
No — only for classes whose instances will be: used as HashMap or LinkedHashMap keys, stored in a HashSet or LinkedHashSet, compared with assertEquals() in unit tests by value, or passed to List.contains() or Collections.frequency() where content equality matters. For service classes, controller classes, or any class used by reference only, the default identity-based implementations are correct and safe.
What is the difference between == and equals() in Java?
== is the reference equality operator — it returns true only when both sides point to the exact same object in memory. equals() is a method that can be overridden to define logical equality. For String, Integer, and Long, equals() compares content. For classes that do not override equals(), it falls back to == behaviour. This is why new String("hello") == new String("hello") is false but .equals() between them is true.
Can two objects be equal but have different class types?
Technically yes — you can write an equals() that returns true for objects of different classes. But this almost always breaks the symmetry contract. If a.equals(b) returns true when b is of a parent or sibling class but b.equals(a) returns false because b's equals() uses instanceof more narrowly, symmetry is violated. The safest approach for inheritance hierarchies is to use getClass() != obj.getClass() instead of instanceof — this rejects cross-type equality entirely and keeps the contract clean.
What is the hashCode() value for a null key in HashMap?
HashMap specifically handles null keys by placing them in bucket 0 (index 0), bypassing the normal hashCode() call. This is why HashMap allows exactly one null key while ConcurrentHashMap and Hashtable do not — those implementations call hashCode() on the key, which throws NullPointerException for null.
Does String already have a good equals() and hashCode()?
Yes. String.equals() compares character content. String.hashCode() uses a deterministic formula: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1], where s[i] is the character at position i. Crucially, String caches the hash code after the first call — subsequent hashCode() calls on the same String return the cached value in O(1), not O(n). This makes String an excellent HashMap key for performance.
What happens if hashCode() returns 0 for all objects?
The map still works correctly — all entries land in bucket 0, which becomes a single enormous chain (or tree after 8 entries). get() degrades from O(1) to O(n) for every lookup because the bucket chain must be traversed fully. A HashMap with 10,000 entries and a constant hashCode() performs like a linked list on every read. No data is lost — correctness is maintained — but performance collapses entirely.
Summary
equals() and hashCode() form a two-part contract that powers every hash-based collection in Java. The critical rule: equal objects must have equal hash codes — always, without exception. Violating this makes HashMap silently lose entries and HashSet silently accept duplicates, with no exception to alert you.
Override both methods together using the same fields. Use Objects.hash() for hashCode() and Objects.equals() for field comparison in equals() — both handle null safely. Only include fields that define the object's identity, and never include mutable fields that change after the object enters a collection.
For interviews: know the contract by heart, know what breaks when hashCode() is overridden without equals() and vice versa, explain why map keys must be immutable, and describe how HashMap uses both methods in its two-step lookup. These questions appear consistently from service-based campus placements to senior product-company rounds, and getting them right shows that you understand how Java collections actually work internally.
What to Read Next
Learn how to safely loop through a collection.
Practice Programs
Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.
Overrides equals() to compare two objects by their field values instead of reference identity.
Overrides hashCode() so it stays consistent with an equals() override.
Shows the default toString(), equals(), and getClass() behavior every class inherits from Object, before any overriding.