เราจะตัดสินใจเกี่ยวกับการนำhashCode()วิธีการที่ดีที่สุดมาใช้ในการรวบรวมได้อย่างไร
collection.hashCode()( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/... )
                เราจะตัดสินใจเกี่ยวกับการนำhashCode()วิธีการที่ดีที่สุดมาใช้ในการรวบรวมได้อย่างไร
collection.hashCode()( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/... )
                คำตอบ:
การใช้งานที่ดีที่สุด? นั่นเป็นคำถามที่ยากเพราะมันขึ้นอยู่กับรูปแบบการใช้งาน
สำหรับเกือบทุกกรณีการดำเนินการที่ดีอย่างสมเหตุสมผลได้ถูกเสนอในชวาที่มีประสิทธิภาพของ Josh Blochในข้อ 8 (รุ่นที่สอง) สิ่งที่ดีที่สุดคือดูที่นั่นเพราะผู้เขียนอธิบายว่าทำไมวิธีการถึงดี
สร้างint resultและกำหนดไม่ใช่ศูนย์ค่า
สำหรับทุกฟิลด์ที่ fทดสอบด้วยequals()วิธีนี้ให้คำนวณรหัสแฮชcโดย:
boolean: คำนวณ(f ? 0 : 1);byte, char, shortหรือint: คำนวณ(int)f;long: คำนวณ(int)(f ^ (f >>> 32));float: คำนวณFloat.floatToIntBits(f);double: คำนวณDouble.doubleToLongBits(f)และจัดการค่าส่งคืนเหมือนทุกค่าที่ยาวhashCode()วิธีการหรือ 0 ถ้าf == null;รวมค่าแฮชcกับresult:
result = 37 * result + cกลับ result
ซึ่งควรส่งผลให้มีการแจกจ่ายค่าแฮชอย่างเหมาะสมสำหรับสถานการณ์การใช้งานส่วนใหญ่
หากคุณพอใจกับการใช้งาน 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) แต่ทำงานในลักษณะเดียวกัน
hashCodeคือถ้าคุณมีการกำหนดเองequalsและนั่นคือสิ่งที่วิธีการไลบรารีเหล่านี้ได้รับการออกแบบมาอย่างถูกต้อง equalsเอกสารที่ค่อนข้างชัดเจนเกี่ยวกับพฤติกรรมของพวกเขาในความสัมพันธ์กับ การดำเนินห้องสมุดไม่ได้เรียกร้องที่จะให้อภัยคุณจากการรู้สิ่งที่ลักษณะของที่ถูกต้องhashCodeดำเนินการเป็น - ห้องสมุดเหล่านี้ทำให้มันง่ายขึ้นสำหรับคุณที่จะดำเนินการดังกล่าวเป็นไปตามกลไกการดำเนินงานส่วนใหญ่ของกรณีที่equalsเป็น overriden
                    java.util.Objects.hash(...)วิธีJDK7 มากกว่าcom.google.common.base.Objects.hashCode(...)วิธีฝรั่ง ฉันคิดว่าคนส่วนใหญ่จะเลือกห้องสมุดมาตรฐานมากกว่าการพึ่งพาพิเศษ
                    hashCode() java.lang.System.identityHashCode(...)
                    มันเป็นการดีกว่าที่จะใช้ฟังก์ชั่นที่จัดทำโดย Eclipse ซึ่งทำงานได้ค่อนข้างดีและคุณสามารถใช้ความพยายามและพลังงานในการพัฒนาตรรกะทางธุรกิจ
แม้ว่าสิ่งนี้จะเชื่อมโยงกับ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));
}
              ก่อนอื่นตรวจสอบให้แน่ใจว่ามีการใช้งานเท่ากับอย่างถูกต้อง จากบทความ 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 () เดียวกัน
 
สุดท้ายฟังก์ชันแฮชที่ดีควรมุ่งมั่นที่จะเข้าใกล้ฟังก์ชั่นที่เหมาะกัญชา
about8.blogspot.com คุณพูดว่า
ถ้าเท่ากับ () ผลตอบแทนจริงสำหรับสองวัตถุแล้ว hashCode () ควรกลับค่าเดียวกัน ถ้าเท่ากับ () ส่งคืนค่าเท็จ hashCode () ควรคืนค่าต่างกัน
ฉันไม่เห็นด้วยกับคุณ หากวัตถุสองชิ้นมีแฮชโค้ดเดียวกันมันไม่จำเป็นต้องหมายความว่าพวกมันเท่ากัน
ถ้า A เท่ากับ B ดังนั้น A.hashcode ต้องเท่ากับ B.hascode
แต่
ถ้า A.hashcode เท่ากับ B.hascode มันไม่ได้หมายความว่า A ต้องเท่ากับ B
(A != B) and (A.hashcode() == B.hashcode())นั่นคือสิ่งที่เราเรียกว่าการชนกันของฟังก์ชันแฮช เป็นเพราะโคโดเมนของฟังก์ชันแฮช จำกัด เสมอในขณะที่โดเมนมักจะไม่ ยิ่งโคโดเมนใหญ่มากเท่าไรการชนก็จะน้อยลง ฟังก์ชั่นแฮชที่ดีควรคืนค่าแฮชที่แตกต่างกันสำหรับวัตถุที่แตกต่างกันโดยมีความเป็นไปได้มากที่สุดที่ทำได้เมื่อกำหนดขนาดโคโดเมน มันแทบจะไม่สามารถรับประกันได้อย่างเต็มที่ว่า
                    หากคุณใช้ eclipse คุณสามารถสร้างequals()และhashCode()ใช้:
แหล่งที่มา -> สร้าง hashCode () และเท่ากับ ()
การใช้ฟังก์ชั่นนี้คุณสามารถเลือกฟิลด์ที่คุณต้องการใช้สำหรับการคำนวณความเท่าเทียมกันและรหัสแฮชและ Eclipse จะสร้างวิธีการที่สอดคล้องกัน
มีการดำเนินงานที่ดีของเรื่องที่มีประสิทธิภาพ Java 's hashcode()และequals()ตรรกะในApache คอมมอนส์แลง กร้าHashCodeBuilderและEqualsBuilder
Objectsชั้นเรียนให้hash(Object ..args)และequals()วิธีการจาก Java7 บน เหมาะสำหรับแอปพลิเคชันที่ใช้ jdk 1.7+
                    IdentityHashMap) FWIW ฉันใช้ hashCode ตามรหัสและเท่ากับเอนทิตีทั้งหมด
                    เพียงบันทึกย่อเพื่อให้ได้คำตอบที่ละเอียดยิ่งขึ้น (ในรูปของโค้ด):
ถ้าฉันพิจารณาคำถามว่าจะทำอย่างไรฉันจะสร้างตารางแฮชในจาวาและโดยเฉพาะอย่างยิ่งรายการคำถามที่พบบ่อยของ jGuruฉันเชื่อว่าเกณฑ์อื่น ๆ ที่ควรใช้รหัสแฮชคือ:
หากฉันเข้าใจคำถามของคุณถูกต้องคุณจะมีคลาสคอลเล็กชันที่กำหนดเอง (เช่นคลาสใหม่ที่ขยายจากส่วนต่อประสาน 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);
}
              @ 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 มันน่าเกลียด)
fooและนำไปสู่การเดียวกันbar AFAIK hashCodeของคุณtoStringไม่ได้คอมไพล์และถ้าเป็นเช่นนั้นแสดงว่ามันไม่มีประสิทธิภาพมาก สิ่งที่ชอบ109 * getFoo().hashCode() + 57 * getBar().hashCode()นั้นเร็วขึ้นง่ายขึ้นและไม่เกิดการชนที่ไม่จำเป็น
                    ตามที่คุณขอเฉพาะคอลเล็กชันฉันต้องการเพิ่มมุมมองที่คำตอบอื่น ๆ ยังไม่ได้กล่าวถึง: HashMap ไม่ได้คาดหวังว่ากุญแจของพวกเขาจะเปลี่ยนรหัส hashcode เมื่อมีการเพิ่มลงในคอลเลกชัน จะเอาชนะวัตถุประสงค์ทั้งหมด ...
ใช้วิธีการสะท้อนบน Apache Commons EqualsBuilderและHashCodeBuilder
ฉันใช้ wrapper เล็ก ๆArrays.deepHashCode(...)เพราะมันจัดการกับอาร์เรย์ที่ให้มาเป็นพารามิเตอร์อย่างถูกต้อง
public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}
              วิธีการแฮชใด ๆ ที่กระจายค่าแฮชอย่างสม่ำเสมอในช่วงที่เป็นไปได้นั้นเป็นการใช้งานที่ดี ดูจาวาที่มีประสิทธิภาพ ( 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 ฉันคิดว่า ... )
ฉันชอบใช้วิธีอรรถประโยชน์จากGoogle คอลเลกชัน lib จากคลาสอ็อบเจ็กต์ที่ช่วยให้ฉันรักษาโค้ดของฉันให้สะอาด บ่อยครั้งมากequalsและhashcodeวิธีการที่ทำจากแม่แบบของ IDE ดังนั้นจึงไม่สะอาดในการอ่าน
นี่คือการสาธิตวิธี 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());
    }
}
              การนำมาตรฐานไปปฏิบัตินั้นอ่อนแอและการใช้มันนำไปสู่การชนที่ไม่จำเป็น ลองนึกภาพ
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
การใช้ตัวทวีคูณที่แตกต่างกันในที่ต่าง ๆ มีประโยชน์ แต่อาจไม่เพียงพอที่จะปรับการทำงานเพิ่มเติมให้เหมาะสม
เมื่อรวมค่าแฮชฉันมักจะใช้วิธีการรวมที่ใช้ในห้องสมุดเพิ่ม c ++ คือ:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
นี่เป็นงานที่ค่อนข้างดีในการรับรองการกระจายอย่างสม่ำเสมอ สำหรับการอภิปรายเกี่ยวกับวิธีการทำงานของสูตรนี้ให้ดูที่โพสต์ StackOverflow: หมายเลข Magic เพื่อเพิ่ม :: hash_combine
มีการอภิปรายที่ดีเกี่ยวกับฟังก์ชั่นแฮชต่างๆได้ที่: http://burtleburtle.net/bob/hash/doobs.html
สำหรับคลาสที่เรียบง่ายมักจะง่ายที่สุดในการใช้ 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 () ควรคืนค่าต่างกัน
("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")ถ้าแฮชโค้ดจะถูกสร้างขึ้นครั้งเดียวจากการเรียงต่อกันของสองสายมันเป็นเรื่องง่ายมากที่จะสร้างฝูงชน: มันเป็นข้อบกพร่องที่รุนแรง มันจะเป็นการดีกว่าที่จะประเมินค่าแฮชโค้ดสำหรับทั้งสองฟิลด์จากนั้นคำนวณชุดค่าผสมเชิงเส้นของฟิลด์เหล่านั้น (ควรใช้เฉพาะช่วงเวลาเป็นค่าสัมประสิทธิ์)
                    fooและbarสร้างการชนที่ไม่มีความจำเป็นเช่นกัน
                    
Objects.hashCode(collection)น่าจะเป็นทางออกที่สมบูรณ์แบบ!