equals(), hashCode(), toString() — The Contract
equals, hashCode, toString — Java-র সবচেয়ে গুরুত্বপূর্ণ চুক্তি
1. Why Every Java Dev Must Know This
Three methods on Object get called constantly by the standard library, most often without you
realising it: equals() (compared by HashMap, HashSet,
List.contains, .equals itself), hashCode() (used as the bucket index
in every hash-based collection), and toString() (used whenever you log, print, or concatenate
an object with a string). Get them wrong and your objects silently misbehave: keys vanish from maps, sets
hold duplicates, logs are unreadable.
Object-এর তিনটি method — equals, hashCode, toString — Java standard library প্রতিনিয়ত ডাকে, প্রায়ই আপনি না জেনে। ভুল করলে: HashMap key খুঁজে পায় না, Set-এ duplicate থাকে, log পড়া যায় না। ঠিকঠাক বানানো অপরিহার্য।
2. The Contract in Five Rules
equals must satisfy:
- Reflexive —
x.equals(x)is always true. - Symmetric — if
x.equals(y), theny.equals(x). - Transitive — if
x.equals(y)andy.equals(z), thenx.equals(z). - Consistent — repeated calls return the same result as long as nothing relevant changes.
x.equals(null)is alwaysfalse.
And the critical link to hashCode:
a.equals(b) is true, then a.hashCode() == b.hashCode() must be true.
যদি
a.equals(b) true হয়, a.hashCode() ও b.hashCode() একই হতে হবে। ভাঙলে HashMap/HashSet সঠিকভাবে কাজ করবে না।
The reverse is not required — two unequal objects may share a hash code (a "collision"), and hash-based collections cope with that. But equal objects producing different hashes is a silent bug.
3. A Correct Implementation — with Objects.equals & Objects.hash
The java.util.Objects helper class does the boring work. Always start with it — it handles
nulls and gives you compact code.
import java.util.Objects;
import java.util.HashMap;
final class NID {
private final String number;
private final String name;
NID(String number, String name) {
this.number = number;
this.name = name;
}
@Override public boolean equals(Object o) {
if (this == o) return true; // quick identity check
if (!(o instanceof NID other)) return false; // type + null in one
return Objects.equals(number, other.number)
&& Objects.equals(name, other.name);
}
@Override public int hashCode() { return Objects.hash(number, name); }
@Override public String toString() { return "NID[" + number + ", " + name + "]"; }
}
class Main {
public static void main(String[] args) {
NID a = new NID("1234567890", "Sumi");
NID b = new NID("1234567890", "Sumi");
System.out.println(a.equals(b)); // true
System.out.println(a.hashCode() == b.hashCode()); // true — contract held
HashMap<NID, String> db = new HashMap<>();
db.put(a, "Dhaka");
System.out.println(db.get(b)); // "Dhaka" — found via b
}
}
4. What Happens If You Override Only equals?
Forgetting hashCode is the classic bug. Two logically-equal objects land in different buckets
of the hash table, and the map loses them.
import java.util.HashSet;
class Broken {
int id;
Broken(int id) { this.id = id; }
@Override public boolean equals(Object o) {
return o instanceof Broken b && b.id == id;
}
// NOTE: no hashCode() — inherits Object's identity-based default
}
class Main {
public static void main(String[] args) {
HashSet<Broken> s = new HashSet<>();
s.add(new Broken(7));
System.out.println(s.contains(new Broken(7)));
// Almost always prints FALSE — hashes differ even though equals() says yes.
}
}
equals override করলে কিন্তু hashCode নয় — HashSet-এ যোগ করা object contains-এ পাওয়া যাবে না। দুটো একসাথে override করা বাধ্যতামূলক।
5. Records Solve This for You
A record auto-generates equals, hashCode, and toString
from its components — correctly, by value. That is one of the strongest reasons to reach for records for
simple data-carrying types.
record NID(String number, String name) {}
class Main {
public static void main(String[] args) {
NID a = new NID("1234567890", "Sumi");
NID b = new NID("1234567890", "Sumi");
System.out.println(a.equals(b)); // true
System.out.println(a); // NID[number=1234567890, name=Sumi]
}
}
6. Vocabulary (শব্দভাণ্ডার)
| Term | Meaning | বাংলায় |
|---|---|---|
| Reference equality | Default — same object in memory (==). | একই object — default। |
| Value equality | What a correct equals expresses — "same content". | একই বিষয়বস্তু — সঠিক equals। |
hashCode() | Integer digest of the object; must match for equals objects. | Object-এর integer digest; equal হলে hashCode-ও সমান হতে হবে। |
| Hash collision | Different objects producing the same hash — allowed. | ভিন্ন object-এর একই hash — অনুমোদিত। |
Objects.equals | Null-safe equality helper. | Null-safe equality helper। |
Objects.hash | Builds a good hash from several components. | কয়েকটি component থেকে ভালো hash। |
toString() | Human-readable form — used in logs and concatenation. | মানুষের পড়ার জন্য form। |
7. Practice Problems
-
Write a class
Coordwith fieldsx,y, correctequals,hashCode, andtoString. Show that a HashSet detects duplicates.Coordclass বানান — x, y field, সঠিকequals,hashCode,toString। HashSet duplicate ধরে কিনা দেখান।✨ Show Answer
Main.javaimport java.util.HashSet; import java.util.Objects; final class Coord { final int x, y; Coord(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { return o instanceof Coord c && c.x == x && c.y == y; } @Override public int hashCode() { return Objects.hash(x, y); } @Override public String toString() { return "(" + x + "," + y + ")"; } } class Main { public static void main(String[] args) { HashSet<Coord> s = new HashSet<>(); s.add(new Coord(1,2)); s.add(new Coord(1,2)); System.out.println(s.size()); // 1 } } -
Two objects have
equalstrue but differenthashCode. Which part of the contract is broken and what practical consequence follows?দুটি object equals true, কিন্তু hashCode আলাদা — contract-এর কোন অংশ ভেঙেছে, বাস্তব সমস্যা কী?✨ Show Answer
Answer: The "equal ⇒ same hash" rule of the
hashCodecontract is broken. Practically, any hash-based collection (HashMap,HashSet,LinkedHashMap) will look for the object in the wrong bucket, socontains/getwill report "not present" even thoughequalssays it is. This is a silent correctness bug."Equal হলে hash-ও equal" নিয়মটি ভাঙছে। ফলে HashMap/HashSet ভুল bucket-এ খুঁজবে — contains/get false দেবে, অথচ equals true। নীরব (silent) bug।
-
Convert this class to a record and show that the behaviour is identical:
class P { int a; int b; ... }.এই class-টিকে record-এ রূপান্তর করে দেখান আচরণ একই:class P { int a; int b; ... }।✨ Show Answer
Main.javarecord P(int a, int b) {} class Main { public static void main(String[] args) { System.out.println(new P(1,2).equals(new P(1,2))); System.out.println(new P(1,2)); } } -
Why should
equalsstart withif (this == o) return true;?equals-এ প্রথমেif (this == o) return true;কেন?✨ Show Answer
Answer: It is a cheap optimisation: reference identity is the strongest possible form of equality, so if it holds we can skip all field comparisons. Since
equals(this)is called surprisingly often (especially inside collection operations), this one-line short-circuit matters.Reference identity সবচেয়ে শক্তিশালী equality — true হলে field তুলনা করার দরকার নেই। Collection operations-এ বারবার কল হয় বলে এই short-circuit কার্যকর।
-
State one rule you must never break when implementing
hashCodefor a mutable class that is used as a HashMap key.Mutable class যদি HashMap key হয়,hashCodeবানানোর সময় কোন একটি নিয়ম কখনো ভাঙা যাবে না — বলুন।✨ Show Answer
Answer: Never include a field in
hashCodewhose value can change while the object is a key in a hash-based collection. If the field changes, the object's hash changes, and the collection will look for it in the wrong bucket — effectively losing it. Either make the equals-relevant fieldsfinal, or do not mutate them while the object is in a map/set.Object যখন HashMap key, তখন hashCode-এ ব্যবহৃত কোনো field পরিবর্তন করা যাবে না — করলে collection সেটিকে ভুল bucket-এ খুঁজবে, অর্থাৎ "হারিয়ে" যাবে।
Summary — Module 19
Every time you put an object into a HashMap or compare two instances with .equals,
you rely on the equals/hashCode contract. Override them together, never just one. Use
Objects.equals and Objects.hash as the safe default. toString is not
part of the contract but hugely improves debugging and logs. For simple data carriers, a
record gives all three correctly for free — and that alone is a great reason to use them.
Objects.equals ও Objects.hash নিরাপদ default। toString চুক্তির অংশ না হলেও log ও debug-এ অমূল্য। সাধারণ data carrier-এর জন্য record তিনটিই সঠিকভাবে বিনামূল্যে দেয়।