เมื่อคุณ override equals() เพื่อนิยามความเท่ากันเชิงตรรกะ คุณ ต้อง override hashCode() ด้วย เพราะคอลเลกชันที่ใช้ hash (HashMap, ) อาศัยสัญญาที่ว่า การฝ่าฝืนสัญญานี้นำไปสู่บั๊กที่ละเอียดอ่อนและหาได้ยาก
HashSet// by default, equals() compares IDENTITY (same object?), hashCode() is based on memory address
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30);
p1.equals(p2); // false by default — different objects, even with same data
หากไม่ override สองอ็อบเจกต์ที่มีเนื้อหาเหมือนกันจะถือว่า "ไม่เท่ากัน" ซึ่งมักไม่ใช่สิ่งที่คุณต้องการสำหรับ 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); // compare by CONTENT
}
@Override
public int hashCode() {
return Objects.hash(name, age); // MUST be consistent with equals()
}
สัญญา: ถ้า a.equals(b) เป็น true แล้ว a.hashCode() == b.hashCode() ต้องเป็น true ด้วย
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30); // equal by our equals()
Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2); // ❌ likely FALSE — even though p1.equals(p2)!
ทำไมน่ะหรือ? HashMap/HashSet ใช้ hashCode() ก่อนเพื่อหา bucket ที่ถูกต้อง จากนั้นจึงใช้ equals() ภายใน bucket นั้น เมื่อใช้ hashCode() ค่าเริ่มต้น (อิงตาม identity) p1 และ p2 จะตกไปอยู่ใน bucket คนละตัว ดังนั้น contains จึงไม่เคยเปรียบเทียบทั้งสองด้วยซ้ำ เซตจึงคิดว่าทั้งสองต่างกัน สิ่งนี้ก่อให้เกิดบั๊กที่งุนงง เช่น มีข้อมูลซ้ำใน Set, การค้นหาใน map ล้มเหลว เป็นต้น
1. equal objects → equal hash codes (REQUIRED for correctness)
2. unequal objects MAY have the same hash (collisions are allowed)
3. hashCode() must be consistent (same object → same code, unchanged)
4. equals() must be reflexive, symmetric, transitive, consistent
record Person(String name, int age) {} // records auto-generate equals/hashCode/toString!
record ของ Java (รวมถึงการ generate ด้วย IDE หรือ Lombok) สร้าง equals/hashCode ที่ถูกต้องและสอดคล้องกันให้คุณ
สัญญาของ equals/hashCode เป็นหนึ่งในกฎที่สำคัญที่สุดของ Java และเป็นกฎที่ถูกฝ่าฝืนบ่อยที่สุดด้วย
การ override equals() โดยไม่ override hashCode() ทำให้คอลเลกชันที่ใช้ hash พังในแบบที่ดีบักได้ยาก: อ็อบเจกต์ที่คุณถือว่าเท่ากันจะ "หายไป" ใน HashMap/HashSet (การค้นหาล้มเหลว, ข้อมูลซ้ำที่เหมือนผี) เพราะมัน hash ไปอยู่คนละ bucket
เนื่องจากคอลเลกชันเหล่านี้พบได้ทุกที่ คลาสที่เป็น value-type ทุกตัวที่ใช้เป็น key หรือสมาชิกของเซตจึงต้อง override ทั้งคู่ อย่างสอดคล้องกัน
การเข้าใจ ทำไม (กลไกการค้นหาแบบ bucket-แล้วค่อย-equals) และการใช้ record หรือโค้ดที่ generate ขึ้นเพื่อให้ถูกต้อง เป็นสิ่งจำเป็นต่อพฤติกรรมที่ถูกต้อง และเป็นหัวข้อสัมภาษณ์คลาสสิกที่เผยให้เห็นความเข้าใจที่แท้จริงว่าคอลเลกชันของ Java ทำงานอย่างไร