การเข้าสู่พื้นที่สีเทาของ "เปิด / ปิดหัวข้อ" แต่จำเป็นต้องขจัดความสับสนเกี่ยวกับคำแนะนำของ Oscar Reyes ว่าการชนกันของแฮชมากขึ้นเป็นสิ่งที่ดีเพราะจะช่วยลดจำนวนองค์ประกอบใน HashMap ฉันอาจเข้าใจผิดในสิ่งที่ออสการ์พูด แต่ดูเหมือนฉันจะไม่ใช่คนเดียว: kdgregory, delfuego, Nash0 และดูเหมือนว่าฉันทุกคนจะมีความเข้าใจ (ผิด ๆ ) เหมือนกัน
ถ้าฉันเข้าใจสิ่งที่ออสการ์พูดเกี่ยวกับคลาสเดียวกันที่มีแฮชโค้ดเดียวกันเขาเสนอว่าจะมีการแทรกอินสแตนซ์ของคลาสเดียวที่มีแฮชโค้ดที่กำหนดไว้ใน HashMap เช่นถ้าฉันมีอินสแตนซ์ของ SomeClass ที่มีแฮชโค้ดเป็น 1 และอินสแตนซ์ที่สองของ SomeClass ที่มีแฮชโค้ดเป็น 1 จะมีการแทรกอินสแตนซ์ของ SomeClass เพียงอินสแตนซ์เดียว
ตัวอย่าง Java pastebin ที่http://pastebin.com/f20af40b9ดูเหมือนจะระบุว่าข้างต้นสรุปสิ่งที่ออสการ์เสนอได้อย่างถูกต้อง
ไม่ว่าความเข้าใจหรือความเข้าใจผิดใด ๆ สิ่งที่เกิดขึ้นคือกรณีที่แตกต่างกันของคลาสเดียวกันไม่ได้ได้รับการแทรกเพียงครั้งเดียวเข้า HashMap ถ้าพวกเขามีแฮชโค้ดเดียวกัน - ไม่ได้จนกว่าจะมีการกำหนดว่ากุญแจที่มีค่าเท่ากันหรือไม่ สัญญาแฮชโค้ดกำหนดให้อ็อบเจ็กต์ที่เท่ากันมีแฮชโค้ดเดียวกัน อย่างไรก็ตามไม่ต้องการให้วัตถุที่ไม่เท่ากันมีรหัสแฮชที่แตกต่างกัน (แม้ว่าสิ่งนี้อาจเป็นที่ต้องการด้วยเหตุผลอื่นก็ตาม) [1]
ตัวอย่าง pastebin.com/f20af40b9 (ซึ่ง Oscar อ้างถึงอย่างน้อยสองครั้ง) ตามหลัง แต่ปรับเปลี่ยนเล็กน้อยเพื่อใช้การยืนยัน JUnit แทนการพิมพ์ ตัวอย่างนี้ใช้เพื่อสนับสนุนข้อเสนอที่ว่ารหัสแฮชเดียวกันทำให้เกิดการชนกันและเมื่อคลาสเหมือนกันจะมีการสร้างรายการเดียวเท่านั้น (เช่นสตริงเดียวในกรณีเฉพาะนี้):
@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
String s = new String("ese");
String ese = new String("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// AND equal
assertTrue(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(2, map.size());
assertEquals(2, map.get("ese"));
assertEquals(3, map.get(some));
assertTrue(s.equals(ese) && s.equals("ese"));
}
class SomeClass {
public int hashCode() {
return 100727;
}
}
อย่างไรก็ตามแฮชโค้ดไม่ใช่เรื่องราวที่สมบูรณ์ สิ่งที่ตัวอย่าง Pastebin ละเลยคือความจริงที่ว่าทั้งคู่s
และese
เท่ากัน: ทั้งคู่เป็นสตริง "ese" ดังนั้นการแทรกหรือรับเนื้อหาของแผนที่โดยใช้s
หรือese
หรือ"ese"
เป็นคีย์จึงเทียบเท่ากันทั้งหมดเนื่องจากs.equals(ese) && s.equals("ese")
เป็นกุญแจสำคัญที่มีทั้งหมดเพราะเทียบเท่า
การทดสอบครั้งที่สองแสดงให้เห็นว่าผิดพลาดที่จะสรุปว่าแฮชโค้ดที่เหมือนกันในคลาสเดียวกันเป็นสาเหตุที่ทำให้คีย์ -> ค่าs -> 1
ถูกเขียนทับese -> 2
เมื่อmap.put(ese, 2)
ถูกเรียกในการทดสอบหนึ่ง ในการทดสอบที่สองs
และese
ยังคงมีแฮชโค้ดเดียวกัน (ตรวจสอบโดยassertEquals(s.hashCode(), ese.hashCode());
) และเป็นคลาสเดียวกัน อย่างไรก็ตามs
และese
เป็นMyString
อินสแตนซ์ในการทดสอบนี้ไม่ใช่String
อินสแตนซ์Java โดยความแตกต่างเดียวที่เกี่ยวข้องกับการทดสอบนี้คือค่าเท่ากับString s equals String ese
ในการทดสอบหนึ่งด้านบนในขณะที่MyStrings s does not equal MyString ese
การทดสอบที่สอง:
@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
MyString s = new MyString("ese");
MyString ese = new MyString("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// BUT not equal
assertFalse(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(3, map.size());
assertEquals(1, map.get(s));
assertEquals(2, map.get(ese));
assertEquals(3, map.get(some));
}
/**
* NOTE: equals is not overridden so the default implementation is used
* which means objects are only equal if they're the same instance, whereas
* the actual Java String class compares the value of its contents.
*/
class MyString {
String i;
MyString(String i) {
this.i = i;
}
@Override
public int hashCode() {
return 100727;
}
}
จากความคิดเห็นในภายหลังออสการ์ดูเหมือนจะย้อนกลับสิ่งที่เขาพูดก่อนหน้านี้และยอมรับความสำคัญของความเท่าเทียม อย่างไรก็ตามดูเหมือนว่าความคิดที่เท่าเทียมกันคือสิ่งที่สำคัญไม่ใช่ "คลาสเดียวกัน" ยังไม่ชัดเจน (เน้นของฉัน):
"ไม่จริงรายการจะถูกสร้างขึ้นก็ต่อเมื่อแฮชเหมือนกัน แต่คีย์แตกต่างกันตัวอย่างเช่นหากสตริงให้แฮชโค้ด 2345 และและจำนวนเต็มให้แฮชโค้ด 2345 เดียวกันดังนั้นจำนวนเต็มจะถูกแทรกลงในรายการเนื่องจากสตริง เท่ากับ (จำนวนเต็ม) เป็นเท็จ แต่ถ้าคุณมีคลาสเดียวกัน (หรืออย่างน้อย .equals ส่งกลับค่าจริง)จะใช้รายการเดียวกันตัวอย่างเช่นสตริงใหม่ ("หนึ่ง") และ "สตริงใหม่ (" หนึ่ง ") ที่ใช้เป็น จะใช้รายการเดียวกันอันที่จริงนี่คือจุดทั้งหมดของ HashMap ตั้งแต่แรก! ดูตัวเอง: pastebin.com/f20af40b9 - Oscar Reyes "
เมื่อเทียบกับความคิดเห็นก่อนหน้านี้ที่กล่าวถึงความสำคัญของคลาสที่เหมือนกันและแฮชโค้ดเดียวกันอย่างชัดเจนโดยไม่มีการกล่าวถึงความเท่าเทียมกัน:
"@delfuego: ดูตัวเอง: pastebin.com/f20af40b9 ดังนั้นในคำถามนี้มีการใช้คลาสเดียวกัน (รอสักครู่คลาสเดียวกันจะถูกใช้ใช่ไหม) ซึ่งหมายความว่าเมื่อแฮชเดียวกันถูกใช้รายการเดียวกัน ถูกใช้และไม่มี "รายชื่อ" ของรายการ - Oscar Reyes "
หรือ
"อันที่จริงสิ่งนี้จะเพิ่มประสิทธิภาพยิ่งมีการชนกันมากขึ้น eq รายการน้อยลงในแฮชแท็ก eq งานที่ต้องทำน้อยลงไม่ใช่แฮช (ซึ่งดูดี) หรือแฮชแท็ก (ซึ่งใช้งานได้ดี) ฉันพนันได้เลยว่ามันอยู่ที่วัตถุ การสร้างที่การแสดงเสื่อมเสีย - Oscar Reyes "
หรือ
"@kdgregory: ใช่ แต่เฉพาะในกรณีที่การชนกันเกิดขึ้นกับคลาสที่แตกต่างกันสำหรับคลาสเดียวกัน (ซึ่งเป็นกรณีนี้) จะใช้รายการเดียวกัน - Oscar Reyes"
อีกครั้งฉันอาจเข้าใจผิดในสิ่งที่ออสการ์พยายามจะพูดจริงๆ อย่างไรก็ตามความคิดเห็นเดิมของเขาทำให้เกิดความสับสนมากพอที่ดูเหมือนว่าจะรอบคอบที่จะเคลียร์ทุกอย่างด้วยการทดสอบที่ชัดเจนดังนั้นจึงไม่มีข้อสงสัยที่ค้างคา
[1] - จากภาษา Java ที่มีประสิทธิภาพฉบับที่สองโดย Joshua Bloch:
เมื่อใดก็ตามที่เรียกใช้บนอ็อบเจ็กต์เดียวกันมากกว่าหนึ่งครั้งในระหว่างการเรียกใช้แอ็พพลิเคชันเมธอด hashCode จะต้องส่งคืนจำนวนเต็มเดียวกันอย่างสม่ำเสมอโดยจะไม่มีการแก้ไขข้อมูลที่ใช้ในการเปรียบเทียบ s ที่เท่ากันบนอ็อบเจ็กต์ จำนวนเต็มนี้ไม่จำเป็นต้องคงที่จากการทำงานของแอปพลิเคชันหนึ่งไปจนถึงการดำเนินการอื่น ๆ
ถ้าวัตถุสองชิ้นมีค่าเท่ากันตามวิธี s (Obj ect) เท่ากันการเรียกใช้เมธอด hashCode บนวัตถุทั้งสองจะต้องให้ผลลัพธ์จำนวนเต็มเท่ากัน
ไม่จำเป็นว่าถ้าสองอ็อบเจกต์ไม่เท่ากันตามเมธอด s (Object) เท่ากันดังนั้นการเรียกเมธอด hashCode บนอ็อบเจ็กต์ทั้งสองแต่ละอ็อบเจ็กต์จะต้องให้ผลลัพธ์จำนวนเต็มที่แตกต่างกัน อย่างไรก็ตามโปรแกรมเมอร์ควรทราบว่าการสร้างผลลัพธ์จำนวนเต็มที่แตกต่างกันสำหรับอ็อบเจ็กต์ที่ไม่เท่ากันอาจปรับปรุงประสิทธิภาพของตารางแฮช