การใช้งานที่ดีที่สุดสำหรับวิธี hashCode สำหรับคอลเลกชัน


299

เราจะตัดสินใจเกี่ยวกับการนำhashCode()วิธีการที่ดีที่สุดมาใช้ในการรวบรวมได้อย่างไร


2
ด้วย Java 7+ ฉันคิดว่าObjects.hashCode(collection)น่าจะเป็นทางออกที่สมบูรณ์แบบ!
Diablo

3
@Diablo ฉันไม่คิดว่าคำตอบว่าคำถามที่ทั้งหมด - วิธีการที่เพียงแค่ผลตอบแทนcollection.hashCode()( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/... )
cbreezier

คำตอบ:


438

การใช้งานที่ดีที่สุด? นั่นเป็นคำถามที่ยากเพราะมันขึ้นอยู่กับรูปแบบการใช้งาน

สำหรับเกือบทุกกรณีการดำเนินการที่ดีอย่างสมเหตุสมผลได้ถูกเสนอในชวาที่มีประสิทธิภาพของ Josh Blochในข้อ 8 (รุ่นที่สอง) สิ่งที่ดีที่สุดคือดูที่นั่นเพราะผู้เขียนอธิบายว่าทำไมวิธีการถึงดี

รุ่นสั้น ๆ

  1. สร้างint resultและกำหนดไม่ใช่ศูนย์ค่า

  2. สำหรับทุกฟิลด์ที่ fทดสอบด้วยequals()วิธีนี้ให้คำนวณรหัสแฮชcโดย:

    • หากฟิลด์ f คือboolean: คำนวณ(f ? 0 : 1);
    • ถ้าเขตฉเป็นbyte, char, shortหรือint: คำนวณ(int)f;
    • หากฟิลด์ f คือlong: คำนวณ(int)(f ^ (f >>> 32));
    • หากฟิลด์ f คือfloat: คำนวณFloat.floatToIntBits(f);
    • หากฟิลด์ f คือdouble: คำนวณDouble.doubleToLongBits(f)และจัดการค่าส่งคืนเหมือนทุกค่าที่ยาว
    • ถ้าฟิลด์ f เป็นวัตถุ : ใช้ผลลัพธ์ของhashCode()วิธีการหรือ 0 ถ้าf == null;
    • หากฟิลด์ f เป็นอาร์เรย์ : ดูทุกฟิลด์เป็นองค์ประกอบแยกต่างหากและคำนวณค่าแฮชในแบบเรียกซ้ำและรวมค่าดังที่อธิบายไว้ถัดไป
  3. รวมค่าแฮชcกับresult:

    result = 37 * result + c
  4. กลับ result

ซึ่งควรส่งผลให้มีการแจกจ่ายค่าแฮชอย่างเหมาะสมสำหรับสถานการณ์การใช้งานส่วนใหญ่


45
ใช่ฉันอยากรู้ว่าหมายเลข 37 มาจากไหน
กีบ

17
ฉันใช้รายการที่ 8 ของหนังสือ "Effective Java" ของ Josh Bloch
dmeister

39
@dma_k เหตุผลสำหรับการใช้ตัวเลขที่สำคัญและวิธีการอธิบายในคำตอบนี้คือเพื่อให้แน่ใจว่าแฮชโค้ดคำนวณจะไม่ซ้ำกัน เมื่อใช้หมายเลขที่ไม่ใช่เฉพาะคุณไม่สามารถรับประกันได้ มันไม่สำคัญว่าคุณเลือกรุ่นไหนดีแค่ไหนไม่มีอะไรมหัศจรรย์เกี่ยวกับหมายเลข 37 (แย่มาก 42 ไม่ใช่เลขตัวเอกใช่มั้ย)
Simon Forsberg

34
@ SimonAndréForsbergรหัสแฮชที่คำนวณไม่สามารถซ้ำได้เสมอ :) เป็นแฮชโค้ด อย่างไรก็ตามฉันมีความคิด: หมายเลขเฉพาะมีตัวคูณเดียวเท่านั้นในขณะที่ผู้ที่ไม่ใช่นายกมีอย่างน้อยสองตัว ที่สร้างชุดค่าผสมพิเศษสำหรับตัวดำเนินการคูณเพื่อส่งผลให้แฮชเดียวกันคือทำให้เกิดการชน
dma_k

14
ผมคิดว่าคูณ Bloch 31 ไม่ 37สำหรับความสะดวกในการเพิ่มประสิทธิภาพ
ruffin

140

หากคุณพอใจกับการใช้งาน Java อย่างมีประสิทธิภาพที่แนะนำโดย dmeister คุณสามารถใช้การโทรห้องสมุดแทนการทำตามขั้นตอนของคุณเอง:

@Override
public int hashCode() {
    return Objects.hashCode(this.firstName, this.lastName);
}

สิ่งนี้ต้องการ Guava ( com.google.common.base.Objects.hashCode) หรือไลบรารี่มาตรฐานใน Java 7 ( java.util.Objects.hash) แต่ทำงานในลักษณะเดียวกัน


8
หากไม่มีเหตุผลที่ดีที่จะไม่ใช้สิ่งเหล่านี้เราควรใช้สิ่งเหล่านี้ในทุกกรณี (กำหนดให้แข็งแกร่งยิ่งขึ้นเนื่องจากควรกำหนดสูตร IMHO) ข้อโต้แย้งทั่วไปสำหรับการใช้งานการใช้งาน / ไลบรารีมาตรฐาน (แนวปฏิบัติที่ดีที่สุดการทดสอบที่ดี
Kissaki

7
@ justin.hughey คุณดูเหมือนจะสับสน กรณีเดียวที่คุณควรแทนที่hashCodeคือถ้าคุณมีการกำหนดเองequalsและนั่นคือสิ่งที่วิธีการไลบรารีเหล่านี้ได้รับการออกแบบมาอย่างถูกต้อง equalsเอกสารที่ค่อนข้างชัดเจนเกี่ยวกับพฤติกรรมของพวกเขาในความสัมพันธ์กับ การดำเนินห้องสมุดไม่ได้เรียกร้องที่จะให้อภัยคุณจากการรู้สิ่งที่ลักษณะของที่ถูกต้องhashCodeดำเนินการเป็น - ห้องสมุดเหล่านี้ทำให้มันง่ายขึ้นสำหรับคุณที่จะดำเนินการดังกล่าวเป็นไปตามกลไกการดำเนินงานส่วนใหญ่ของกรณีที่equalsเป็น overriden
บาคาร่า

6
สำหรับนักพัฒนา Android ที่ดู java.util.Objects class มันถูกนำมาใช้ใน API 19 เท่านั้นดังนั้นตรวจสอบให้แน่ใจว่าคุณใช้งาน KitKat ขึ้นไปมิฉะนั้นคุณจะได้รับ NoClassDefFoundError
Andrew Kelly

3
คำตอบที่ดีที่สุด IMO แม้ว่าโดยตัวอย่างฉันจะเลือกjava.util.Objects.hash(...)วิธีJDK7 มากกว่าcom.google.common.base.Objects.hashCode(...)วิธีฝรั่ง ฉันคิดว่าคนส่วนใหญ่จะเลือกห้องสมุดมาตรฐานมากกว่าการพึ่งพาพิเศษ
Malte Skoruppa

2
หากมีสองข้อโต้แย้งหรือมากกว่าและถ้าใด ๆ ของพวกเขาเป็นอาร์เรย์ผลที่ได้อาจจะไม่ใช่สิ่งที่คุณคาดหวังเพราะสำหรับอาร์เรย์เป็นเพียงของhashCode() java.lang.System.identityHashCode(...)
starikoff

59

มันเป็นการดีกว่าที่จะใช้ฟังก์ชั่นที่จัดทำโดย Eclipse ซึ่งทำงานได้ค่อนข้างดีและคุณสามารถใช้ความพยายามและพลังงานในการพัฒนาตรรกะทางธุรกิจ


4
+1 ทางออกที่ดี คำตอบของ dmeister นั้นครอบคลุมมากขึ้น แต่ฉันมักจะลืมจัดการกับโมฆะเมื่อฉันพยายามเขียน hashcodes ด้วยตัวเอง
Quantum7

1
+1 เห็นด้วยกับ Quantum7 แต่ฉันจะบอกว่ามันเป็นเรื่องที่ดีมากที่จะเข้าใจว่าการใช้งานที่สร้างโดย Eclipse นั้นเป็นอย่างไรและได้รับรายละเอียดการนำไปปฏิบัติที่ใด
jwir3

15
ขออภัย แต่คำตอบที่เกี่ยวข้องกับ "ฟังก์ชั่นที่จัดทำโดย [บาง IDE]" นั้นไม่เกี่ยวข้องกับบริบทของภาษาการเขียนโปรแกรมโดยทั่วไป มีหลายสิบ IDEs และนี่ไม่ตอบคำถาม ... นั่นเป็นเพราะนี่คือเพิ่มเติมเกี่ยวกับการกำหนดอัลกอริทึมและเกี่ยวข้องโดยตรงกับการดำเนินการเท่ากับ () - สิ่งที่ IDE จะไม่รู้อะไรเกี่ยวกับ
Darrell Teague

57

แม้ว่าสิ่งนี้จะเชื่อมโยงกับAndroidเอกสาร (เครื่อง Wayback)และรหัสของฉันเองบน Githubแต่มันจะใช้ได้กับ Java โดยทั่วไป คำตอบของฉันคือส่วนขยายของคำตอบของ dmeisterด้วยรหัสที่อ่านและเข้าใจได้ง่ายกว่ามาก

@Override 
public int hashCode() {

    // Start with a non-zero constant. Prime is preferred
    int result = 17;

    // Include a hash for each field.

    // Primatives

    result = 31 * result + (booleanField ? 1 : 0);                   // 1 bit   » 32-bit

    result = 31 * result + byteField;                                // 8 bits  » 32-bit 
    result = 31 * result + charField;                                // 16 bits » 32-bit
    result = 31 * result + shortField;                               // 16 bits » 32-bit
    result = 31 * result + intField;                                 // 32 bits » 32-bit

    result = 31 * result + (int)(longField ^ (longField >>> 32));    // 64 bits » 32-bit

    result = 31 * result + Float.floatToIntBits(floatField);         // 32 bits » 32-bit

    long doubleFieldBits = Double.doubleToLongBits(doubleField);     // 64 bits (double) » 64-bit (long) » 32-bit (int)
    result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));

    // Objects

    result = 31 * result + Arrays.hashCode(arrayField);              // var bits » 32-bit

    result = 31 * result + referenceField.hashCode();                // var bits » 32-bit (non-nullable)   
    result = 31 * result +                                           // var bits » 32-bit (nullable)   
        (nullableReferenceField == null
            ? 0
            : nullableReferenceField.hashCode());

    return result;

}

แก้ไข

โดยปกติเมื่อคุณแทนที่คุณยังต้องการที่จะแทนที่hashcode(...) equals(...)ดังนั้นสำหรับผู้ที่จะหรือนำไปใช้แล้วequalsนี่เป็นข้อมูลอ้างอิงที่ดีจาก Github ของฉัน ...

@Override
public boolean equals(Object o) {

    // Optimization (not required).
    if (this == o) {
        return true;
    }

    // Return false if the other object has the wrong type, interface, or is null.
    if (!(o instanceof MyType)) {
        return false;
    }

    MyType lhs = (MyType) o; // lhs means "left hand side"

            // Primitive fields
    return     booleanField == lhs.booleanField
            && byteField    == lhs.byteField
            && charField    == lhs.charField
            && shortField   == lhs.shortField
            && intField     == lhs.intField
            && longField    == lhs.longField
            && floatField   == lhs.floatField
            && doubleField  == lhs.doubleField

            // Arrays

            && Arrays.equals(arrayField, lhs.arrayField)

            // Objects

            && referenceField.equals(lhs.referenceField)
            && (nullableReferenceField == null
                        ? lhs.nullableReferenceField == null
                        : nullableReferenceField.equals(lhs.nullableReferenceField));
}

1
เอกสาร Android ตอนนี้ไม่รวมรหัสข้างต้นอีกต่อไปดังนั้นนี่เป็นรุ่นที่เก็บไว้ในเครื่อง Wayback - เอกสาร Android (07 ก.พ. 2558)
Christopher Rucinski

17

ก่อนอื่นตรวจสอบให้แน่ใจว่ามีการใช้งานเท่ากับอย่างถูกต้อง จากบทความ IBM DeveloperWorks :

  • สมมาตร: สำหรับการอ้างอิงสองรายการคือ a และ b, a.equals (b) ถ้าและถ้า b.equals (a) เท่านั้น
  • Reflexivity: สำหรับการอ้างอิงที่ไม่เป็นโมฆะทั้งหมด, a.equals (a)
  • ความอ่อนไหว: ถ้า a.equals (b) และ b.equals (c) ดังนั้น a.equals (c)

จากนั้นตรวจสอบให้แน่ใจว่าความสัมพันธ์ของพวกเขากับ hashCode เคารพผู้ติดต่อ (จากบทความเดียวกัน):

  • ความสอดคล้องกับ hashCode (): สองวัตถุที่เท่ากันจะต้องมีค่า hashCode () เดียวกัน

สุดท้ายฟังก์ชันแฮชที่ดีควรมุ่งมั่นที่จะเข้าใกล้ฟังก์ชั่นที่เหมาะกัญชา


11

about8.blogspot.com คุณพูดว่า

ถ้าเท่ากับ () ผลตอบแทนจริงสำหรับสองวัตถุแล้ว hashCode () ควรกลับค่าเดียวกัน ถ้าเท่ากับ () ส่งคืนค่าเท็จ hashCode () ควรคืนค่าต่างกัน

ฉันไม่เห็นด้วยกับคุณ หากวัตถุสองชิ้นมีแฮชโค้ดเดียวกันมันไม่จำเป็นต้องหมายความว่าพวกมันเท่ากัน

ถ้า A เท่ากับ B ดังนั้น A.hashcode ต้องเท่ากับ B.hascode

แต่

ถ้า A.hashcode เท่ากับ B.hascode มันไม่ได้หมายความว่า A ต้องเท่ากับ B


3
ถ้า(A != B) and (A.hashcode() == B.hashcode())นั่นคือสิ่งที่เราเรียกว่าการชนกันของฟังก์ชันแฮช เป็นเพราะโคโดเมนของฟังก์ชันแฮช จำกัด เสมอในขณะที่โดเมนมักจะไม่ ยิ่งโคโดเมนใหญ่มากเท่าไรการชนก็จะน้อยลง ฟังก์ชั่นแฮชที่ดีควรคืนค่าแฮชที่แตกต่างกันสำหรับวัตถุที่แตกต่างกันโดยมีความเป็นไปได้มากที่สุดที่ทำได้เมื่อกำหนดขนาดโคโดเมน มันแทบจะไม่สามารถรับประกันได้อย่างเต็มที่ว่า
Krzysztof Jabłoński

นี่เป็นเพียงความคิดเห็นที่โพสต์ข้างบนเป็นสีเทา ข้อมูลที่ดี แต่ไม่ได้ตอบคำถามจริงๆ
Christopher Rucinski

ความคิดเห็นที่ดี แต่ต้องระวังเกี่ยวกับการใช้คำว่า 'object ที่แตกต่าง' ... เพราะเท่ากับ () และดังนั้นการใช้ hashCode () ไม่จำเป็นต้องเกี่ยวกับวัตถุที่แตกต่างกันในบริบท OO แต่มักจะเป็นตัวแทนโมเดลโดเมนของพวกเขา (เช่นสอง คนสามารถถือว่าเหมือนกันหากพวกเขาแบ่งปันรหัสประเทศและรหัสประเทศ - แม้ว่าสิ่งเหล่านี้อาจเป็น 'วัตถุ' ที่แตกต่างกันสองรายการใน JVM - พวกเขาถูกพิจารณาว่า 'เท่าเทียมกัน' และมี hashCode ที่กำหนด) ...
Darrell Teague

7

หากคุณใช้ eclipse คุณสามารถสร้างequals()และhashCode()ใช้:

แหล่งที่มา -> สร้าง hashCode () และเท่ากับ ()

การใช้ฟังก์ชั่นนี้คุณสามารถเลือกฟิลด์ที่คุณต้องการใช้สำหรับการคำนวณความเท่าเทียมกันและรหัสแฮชและ Eclipse จะสร้างวิธีการที่สอดคล้องกัน


7

มีการดำเนินงานที่ดีของเรื่องที่มีประสิทธิภาพ Java 's hashcode()และequals()ตรรกะในApache คอมมอนส์แลง กร้าHashCodeBuilderและEqualsBuilder


1
ข้อเสียของ API นี้คือคุณต้องเสียค่าใช้จ่ายในการสร้างวัตถุทุกครั้งที่คุณโทรเท่ากับและ hashcode (ยกเว้นว่าวัตถุของคุณไม่เปลี่ยนรูปและคุณจะคำนวณค่าแฮชล่วงหน้า) ซึ่งอาจมีจำนวนมากในบางกรณี
James McMahon

นี่เป็นวิธีที่ฉันชอบจนกระทั่งเมื่อไม่นานมานี้ ฉันได้พบกับ StackOverFlowError ขณะที่ใช้เกณฑ์สำหรับการเชื่อมโยง SharedKey OneToOne มากกว่าObjectsชั้นเรียนให้hash(Object ..args)และequals()วิธีการจาก Java7 บน เหมาะสำหรับแอปพลิเคชันที่ใช้ jdk 1.7+
Diablo

@Diablo ฉันเดาว่าปัญหาของคุณเป็นวัฏจักรในกราฟวัตถุแล้วคุณก็โชคไม่ดีที่มีการนำไปใช้งานส่วนใหญ่เนื่องจากคุณไม่ต้องสนใจการอ้างอิงหรือทำลายวงจร (บังคับIdentityHashMap) FWIW ฉันใช้ hashCode ตามรหัสและเท่ากับเอนทิตีทั้งหมด
maaartinus

6

เพียงบันทึกย่อเพื่อให้ได้คำตอบที่ละเอียดยิ่งขึ้น (ในรูปของโค้ด):

ถ้าฉันพิจารณาคำถามว่าจะทำอย่างไรฉันจะสร้างตารางแฮชในจาวาและโดยเฉพาะอย่างยิ่งรายการคำถามที่พบบ่อยของ jGuruฉันเชื่อว่าเกณฑ์อื่น ๆ ที่ควรใช้รหัสแฮชคือ:

  • การประสาน (algo รองรับการเข้าถึงพร้อมกันหรือไม่)?
  • การทำซ้ำล้มเหลวอย่างปลอดภัย (algo ตรวจพบคอลเลกชันที่เปลี่ยนแปลงในระหว่างการทำซ้ำ)
  • ค่า Null (รหัสแฮชสนับสนุนค่า Null ในคอลเล็กชัน) หรือไม่

4

หากฉันเข้าใจคำถามของคุณถูกต้องคุณจะมีคลาสคอลเล็กชันที่กำหนดเอง (เช่นคลาสใหม่ที่ขยายจากส่วนต่อประสาน Collection) และคุณต้องการใช้เมธอด hashCode ()

หากคลาสการรวบรวมของคุณขยาย AbstractList คุณไม่ต้องกังวลเกี่ยวกับมันมีการใช้งานเท่ากับ () และ hashCode () ที่ทำงานโดยการวนซ้ำผ่านวัตถุทั้งหมดและเพิ่ม hashCodes () เข้าด้วยกัน

   public int hashCode() {
      int hashCode = 1;
      Iterator i = iterator();
      while (i.hasNext()) {
        Object obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
      }
  return hashCode;
   }

ตอนนี้ถ้าสิ่งที่คุณต้องการเป็นวิธีที่ดีที่สุดในการคำนวณรหัสแฮชสำหรับคลาสเฉพาะฉันใช้ตัวดำเนินการ ^ (bitwise exclusive หรือ) ในการประมวลผลทุกฟิลด์ที่ฉันใช้ในวิธีการเท่ากับ:

public int hashCode(){
   return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}

2

@ about8: มีข้อผิดพลาดที่ร้ายแรงอยู่ที่นั่น

Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");

แฮชโค้ดเดียวกัน

คุณอาจต้องการบางสิ่งบางอย่าง

public int hashCode() {
    return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();

(คุณสามารถรับ hashCode โดยตรงจาก int ใน Java วันนี้หรือไม่ฉันคิดว่าการ autocasting บางอย่าง .. ถ้าเป็นกรณีนี้ให้ข้าม toString มันน่าเกลียด)


3
ข้อผิดพลาดคือคำตอบที่ยาวโดย about8.blogspot.com การรับ hashcode จากการต่อสายจะทำให้คุณมีฟังก์ชันแฮชที่เหมือนกันสำหรับการรวมกันของสตริงที่รวมกันเป็นสตริงเดียวกัน
SquareCog

1
ดังนั้นนี่คือการสนทนาเมตาและไม่เกี่ยวข้องกับคำถามเลย? ;-)
Huppie

1
เป็นการแก้ไขคำตอบที่เสนอซึ่งมีข้อบกพร่องที่สำคัญพอสมควร
SquareCog

นี่เป็นการดำเนินการที่ จำกัด มาก
Christopher Rucinski

การใช้งานของคุณหลีกเลี่ยงปัญหาและแนะนำสิ่งอื่น; การแลกเปลี่ยนfooและนำไปสู่การเดียวกันbar AFAIK hashCodeของคุณtoStringไม่ได้คอมไพล์และถ้าเป็นเช่นนั้นแสดงว่ามันไม่มีประสิทธิภาพมาก สิ่งที่ชอบ109 * getFoo().hashCode() + 57 * getBar().hashCode()นั้นเร็วขึ้นง่ายขึ้นและไม่เกิดการชนที่ไม่จำเป็น
maaartinus

2

ตามที่คุณขอเฉพาะคอลเล็กชันฉันต้องการเพิ่มมุมมองที่คำตอบอื่น ๆ ยังไม่ได้กล่าวถึง: HashMap ไม่ได้คาดหวังว่ากุญแจของพวกเขาจะเปลี่ยนรหัส hashcode เมื่อมีการเพิ่มลงในคอลเลกชัน จะเอาชนะวัตถุประสงค์ทั้งหมด ...


2

ใช้วิธีการสะท้อนบน Apache Commons EqualsBuilderและHashCodeBuilder


1
หากคุณกำลังจะใช้สิ่งนี้โปรดทราบว่าภาพสะท้อนนั้นมีราคาแพง ฉันสุจริตจะไม่ใช้สิ่งนี้นอกเหนือจากการทิ้งรหัส
James McMahon

2

ฉันใช้ wrapper เล็ก ๆArrays.deepHashCode(...)เพราะมันจัดการกับอาร์เรย์ที่ให้มาเป็นพารามิเตอร์อย่างถูกต้อง

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}

1

วิธีการแฮชใด ๆ ที่กระจายค่าแฮชอย่างสม่ำเสมอในช่วงที่เป็นไปได้นั้นเป็นการใช้งานที่ดี ดูจาวาที่มีประสิทธิภาพ ( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result ) มีเคล็ดลับที่ดี ในนั้นสำหรับการติดตั้งแฮชโค้ด (ข้อ 9 ฉันคิดว่า ... )


1

ฉันชอบใช้วิธีอรรถประโยชน์จากGoogle คอลเลกชัน lib จากคลาสอ็อบเจ็กต์ที่ช่วยให้ฉันรักษาโค้ดของฉันให้สะอาด บ่อยครั้งมากequalsและhashcodeวิธีการที่ทำจากแม่แบบของ IDE ดังนั้นจึงไม่สะอาดในการอ่าน


1

นี่คือการสาธิตวิธี JDK 1.7+ อีกหนึ่งเรื่องที่มีการบันทึก superclass ฉันเห็นว่าค่อนข้างมั่นใจกับ Object class hashCode () ซึ่งมีการอ้างอิงการพึ่งพา JDK อย่างแท้จริงและไม่มีการใช้งานแบบพิเศษเพิ่มเติม โปรดทราบว่าObjects.hash()เป็นโมฆะทน

ฉันไม่ได้รวมequals()การใช้งานใด ๆแต่ในความเป็นจริงแล้วคุณจะต้องการมันแน่นอน

import java.util.Objects;

public class Demo {

    public static class A {

        private final String param1;

        public A(final String param1) {
            this.param1 = param1;
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param1);
        }

    }

    public static class B extends A {

        private final String param2;
        private final String param3;

        public B(
            final String param1,
            final String param2,
            final String param3) {

            super(param1);
            this.param2 = param2;
            this.param3 = param3;
        }

        @Override
        public final int hashCode() {
            return Objects.hash(
                super.hashCode(),
                this.param2,
                this.param3);
        }
    }

    public static void main(String [] args) {

        A a = new A("A");
        B b = new B("A", "B", "C");

        System.out.println("A: " + a.hashCode());
        System.out.println("B: " + b.hashCode());
    }

}

1

การนำมาตรฐานไปปฏิบัตินั้นอ่อนแอและการใช้มันนำไปสู่การชนที่ไม่จำเป็น ลองนึกภาพ

class ListPair {
    List<Integer> first;
    List<Integer> second;

    ListPair(List<Integer> first, List<Integer> second) {
        this.first = first;
        this.second = second;
    }

    public int hashCode() {
        return Objects.hashCode(first, second);
    }

    ...
}

ตอนนี้

new ListPair(List.of(a), List.of(b, c))

และ

new ListPair(List.of(b), List.of(a, c))

มีเหมือนกันhashCodeคือ31*(a+b) + cตัวคูณที่ใช้สำหรับการList.hashCodeใช้ซ้ำที่นี่ เห็นได้ชัดว่าการชนนั้นไม่สามารถหลีกเลี่ยงได้ แต่การสร้างการชนที่ไม่มีความจำเป็นนั้นเป็นเพียง ... ไม่จำเป็น

31ไม่มีอะไรที่มีนัยสำคัญเกี่ยวกับการใช้สมาร์ทเป็น ตัวคูณจะต้องเป็นเลขคี่เพื่อหลีกเลี่ยงการสูญเสียข้อมูล (ตัวคูณใด ๆ ที่สูญหายอย่างน้อยบิตที่สำคัญที่สุดทวีคูณของสี่สูญเสียสองเป็นต้น) ตัวคูณคี่ใด ๆ สามารถใช้งานได้ ตัวคูณขนาดเล็กอาจนำไปสู่การคำนวณที่เร็วขึ้น (JIT สามารถใช้การเลื่อนและการเพิ่มเติม) แต่เนื่องจากการคูณมีความหน่วงแฝงเพียงสามรอบในปัจจุบันของ Intel / AMD สิ่งนี้แทบจะไม่สำคัญ ตัวคูณขนาดเล็กยังนำไปสู่การชนกันมากขึ้นสำหรับอินพุตขนาดเล็กซึ่งบางครั้งอาจมีปัญหา

การใช้งานไพรม์นั้นไม่มีจุดหมายเนื่องจากช่วงเวลาไม่มีความหมายในวงแหวน Z / (2 ** 32)

ดังนั้นฉันขอแนะนำให้ใช้หมายเลขคี่ขนาดใหญ่ที่สุ่มเลือก (อย่าลังเลที่จะเลือกนายก) ในขณะที่ซีพียู i86 / amd64 สามารถใช้คำสั่งที่สั้นกว่าสำหรับตัวถูกดำเนินการติดตั้งในไบต์เดียวที่ลงนามจึงมีข้อได้เปรียบความเร็วเล็ก ๆ สำหรับตัวคูณทวีคูณเช่น 109 สำหรับการลดการชนให้ใช้ 0x58a54cf5

การใช้ตัวทวีคูณที่แตกต่างกันในที่ต่าง ๆ มีประโยชน์ แต่อาจไม่เพียงพอที่จะปรับการทำงานเพิ่มเติมให้เหมาะสม


0

เมื่อรวมค่าแฮชฉันมักจะใช้วิธีการรวมที่ใช้ในห้องสมุดเพิ่ม c ++ คือ:

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

นี่เป็นงานที่ค่อนข้างดีในการรับรองการกระจายอย่างสม่ำเสมอ สำหรับการอภิปรายเกี่ยวกับวิธีการทำงานของสูตรนี้ให้ดูที่โพสต์ StackOverflow: หมายเลข Magic เพื่อเพิ่ม :: hash_combine

มีการอภิปรายที่ดีเกี่ยวกับฟังก์ชั่นแฮชต่างๆได้ที่: http://burtleburtle.net/bob/hash/doobs.html


1
นี่เป็นคำถามเกี่ยวกับ Java ไม่ใช่ C ++
dano

-1

สำหรับคลาสที่เรียบง่ายมักจะง่ายที่สุดในการใช้ hashCode () ตามฟิลด์คลาสที่ถูกตรวจสอบโดยการดำเนินการเท่ากับ ()

public class Zam {
    private String foo;
    private String bar;
    private String somethingElse;

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        Zam otherObj = (Zam)obj;

        if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
            if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
                return true;
            }
        }

        return false;
    }

    public int hashCode() {
        return (getFoo() + getBar()).hashCode();
    }

    public String getFoo() {
        return foo;
    }

    public String getBar() {
        return bar;
    }
}

สิ่งที่สำคัญที่สุดคือการทำให้ hashCode () และเท่ากับ () สอดคล้องกัน: ถ้าเท่ากับ () ผลตอบแทนจริงสำหรับวัตถุสองแล้ว hashCode () ควรกลับค่าเดียวกัน ถ้าเท่ากับ () ส่งคืนค่าเท็จ hashCode () ควรคืนค่าต่างกัน


1
เช่นเดียวกับ SquareCog ได้สังเกตเห็นแล้ว ("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")ถ้าแฮชโค้ดจะถูกสร้างขึ้นครั้งเดียวจากการเรียงต่อกันของสองสายมันเป็นเรื่องง่ายมากที่จะสร้างฝูงชน: มันเป็นข้อบกพร่องที่รุนแรง มันจะเป็นการดีกว่าที่จะประเมินค่าแฮชโค้ดสำหรับทั้งสองฟิลด์จากนั้นคำนวณชุดค่าผสมเชิงเส้นของฟิลด์เหล่านั้น (ควรใช้เฉพาะช่วงเวลาเป็นค่าสัมประสิทธิ์)
Krzysztof Jabłoński

@ KrzysztofJabłońskiถูกต้อง นอกจากนี้การแลกเปลี่ยนfooและbarสร้างการชนที่ไม่มีความจำเป็นเช่นกัน
maaartinus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.