Khi bạn override equals() để định nghĩa sự bằng nhau về mặt logic, bạn phải đồng thời override hashCode() — vì các collection dựa trên hash (HashMap, ) dựa vào hợp đồng rằng . Phá vỡ điều này dẫn đến các bug tinh vi, khó tìm.
HashSet// mặc định, equals() so sánh IDENTITY (cùng object?), hashCode() dựa trên địa chỉ bộ nhớ
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30);
p1.equals(p2); // false theo mặc định — khác object, dù cùng dữ liệu
Nếu không override, hai object có nội dung giống hệt nhau sẽ "không bằng nhau" — thường không phải điều bạn muốn với value object.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name); // so sánh theo NỘI DUNG
}
@Override
public int hashCode() {
return Objects.hash(name, age); // PHẢI nhất quán với equals()
}
Hợp đồng: nếu a.equals(b) là true, thì a.hashCode() == b.hashCode() PHẢI là true.
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30); // bằng nhau theo equals() của ta
Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2); // ❌ rất có thể FALSE — mặc dù p1.equals(p2)!
Tại sao? HashMap/HashSet trước tiên dùng hashCode() để tìm đúng bucket, rồi dùng equals() bên trong nó. Với hashCode() mặc định (dựa trên identity), p1 và p2 rơi vào các bucket khác nhau, nên contains thậm chí không bao giờ so sánh chúng — set tưởng rằng chúng khác nhau. Điều này gây ra các bug khó hiểu: phần tử trùng trong một Set, tra cứu map thất bại, v.v.
1. object bằng nhau → hash code bằng nhau (BẮT BUỘC cho tính đúng đắn)
2. object không bằng nhau CÓ THỂ có cùng hash (cho phép collision)
3. hashCode() phải nhất quán (cùng object → cùng code, không đổi)
4. equals() phải có tính phản xạ, đối xứng, bắc cầu, nhất quán
record Person(String name, int age) {} // record tự sinh equals/hashCode/toString!
Record của Java (cùng với việc IDE sinh code / Lombok) tạo ra equals/hashCode đúng và nhất quán giúp bạn.
Hợp đồng equals/hashCode là một trong những quy tắc quan trọng nhất — và bị vi phạm phổ biến nhất — của Java.
Override equals() mà không override hashCode() làm hỏng các collection dựa trên hash theo cách khó debug: các object mà bạn cho là bằng nhau bị "lạc" trong HashMap/HashSet (tra cứu thất bại, phần tử trùng ma) vì chúng hash vào các bucket khác nhau.
Vì các collection này có mặt khắp nơi, mọi class kiểu giá trị được dùng làm key hay phần tử của set đều phải override cả hai một cách nhất quán.
Hiểu tại sao (cơ chế tra cứu bucket-rồi-equals) — và dùng record hoặc code được sinh tự động để làm đúng — là thiết yếu cho hành vi đúng đắn và là chủ đề phỏng vấn kinh điển bộc lộ sự hiểu biết thực sự về cách các collection của Java hoạt động.