Họ đang đo gì
Bạn có hiểu hợp đồng giữa hai phương thức này không, và có biết cấu trúc băm dùng chúng theo thứ tự nào.
Trả lời ngắn~30 giây
Vì hợp đồng nói: hai object bằng nhau theo equals() thì BẮT BUỘC có cùng hashCode(). HashMap tìm bucket bằng hash trước rồi mới so equals() trong bucket đó — nên nếu hash khác nhau, nó tìm sai bucket và không bao giờ gọi tới equals(). Kết quả là bạn put một object rồi get bằng object bằng nó mà nhận null.
Giải thích sâu
Chiều ngược lại không bắt buộc: hai object có cùng hashCode không nhất thiết bằng nhau. Đó gọi là va chạm, và nó hoàn toàn hợp lệ — HashMap xử lý bằng cách so equals() trong bucket. Nghĩa là một hashCode() luôn trả về hằng số vẫn ĐÚNG hợp đồng, chỉ là nó biến map thành một danh sách liên kết và mọi thao tác thành O(n).
Cái bẫy nguy hiểm hơn là dùng trường CÓ THỂ THAY ĐỔI để tính hash. Bạn bỏ object vào HashSet, rồi sửa trường đó, hash đổi theo, và object nằm lại đúng bucket cũ — giờ contains() trả về false cho chính object đang nằm trong set. Vì vậy khoá của map nên bất biến, và đây là một lý do rất thực tế để dùng record từ Java 16 trở đi.
// record sinh sẵn equals/hashCode/toString từ các thành phần
record UserId(String tenant, long id) {}
var set = new HashSet<UserId>();
set.add(new UserId("acme", 7));
set.contains(new UserId("acme", 7)); // true
// Lớp chỉ ghi đè equals: biên dịch được, và hỏng ngay
class Bad { int id; public boolean equals(Object o) { /* … */ } }
var m = new HashMap<Bad, String>();
m.put(a, "x");
m.get(b); // null, dù a.equals(b) là trueCâu hỏi tiếp theo họ sẽ hỏi
?== và equals() khác gì?
== so sánh tham chiếu (hoặc giá trị với kiểu nguyên thuỷ); equals() so sánh nội dung theo cách lớp định nghĩa. Với String thì == có thể tình cờ đúng nhờ string pool, và đó chính là chỗ khiến người mới tin nhầm rằng nó luôn đúng.
Trả lời thế này là mất điểm
- Nói “hashCode trả về địa chỉ bộ nhớ”. Không phải, và HotSpot còn lưu giá trị đã tính vào object header.