Najlepsza implementacja metody hashCode dla zbioru

Jak wybrać najlepszą implementację metody hashCode() dla zbioru (zakładając, że metoda equals została poprawnie nadpisana)?

Author: Basil Bourque, 2008-09-22

20 answers

Najlepsza realizacja? Jest to trudne pytanie, ponieważ zależy to od wzorca użytkowania.

A dla prawie wszystkich przypadków rozsądne dobre wdrożenie zaproponowano w Josh Bloch ' s skuteczna Java W poz. 8 (wydanie drugie). Najlepiej zajrzeć tam, bo autor wyjaśnia, dlaczego podejście jest dobre.

Krótka wersja

  1. Tworzymy int result i przypisujemy niezerowe wartość.

  2. Dla każdego pola f testowane w metodzie equals(), Oblicz kod hash c według:

    • jeśli pole f jest boolean: Oblicz (f ? 0 : 1);
    • jeśli pole f jest byte, char, short or int: Oblicz (int)f;
    • jeśli pole f jest long: Oblicz (int)(f ^ (f >>> 32));
    • jeśli pole f jest float: Oblicz Float.floatToIntBits(f);
    • jeśli pole f jest double: Oblicz Double.doubleToLongBits(f) i obsłuż wartość zwracaną jak każde długie wartość;
    • jeśli pole f jest obiektem : użyj wyniku metody hashCode() lub 0 jeśli f == null;
    • jeśli pole f jest tablicą : zobacz każde pole jako oddzielny element i Oblicz wartość skrótu w sposób rekurencyjny i połącz wartości zgodnie z opisem poniżej.
  3. Połącz wartość skrótu c z result:

    result = 37 * result + c
    
  4. Return {result

Powinno to skutkować prawidłowym rozkładem wartości skrótu dla większości sytuacji użycia.

 397
Author: dmeister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-02 16:19:52

Jeśli jesteś zadowolony z efektywnej implementacji Javy zalecanej przez dmeister, możesz użyć wywołania biblioteki zamiast wywoływania własnej:

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

To wymaga albo guava (com.google.common.base.Objects.hashCode(...)) lub JDK7 (java.util.Objects.hash(...)), ale działa w ten sam sposób.

 119
Author: bacar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-05 20:03:44

Lepiej jest skorzystać z funkcjonalności dostarczanej przez Eclipse, która robi całkiem dobrą robotę i można włożyć swój wysiłek i energię w rozwój logiki biznesowej.

 59
Author: Warrior,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-29 06:30:41

Chociaż jest to związane z Android Dokumentacja (Wayback Machine) i mój własny kod na Github , będzie działał ogólnie w Javie. Moja odpowiedź jest rozszerzeniem odpowiedzi dmeistera z kodem, który jest znacznie łatwiejszy do odczytania i zrozumienia.

@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;

}

EDIT

Zazwyczaj, gdy nadpisujesz hashcode(...), chcesz również nadpisać equals(...). Więc dla tych, którzy będą lub już zaimplementowali equals, oto dobra Referencja z mojego 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));
}
 51
Author: Christopher Rucinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-05 15:01:42

Najpierw upewnij się, że equals jest poprawnie zaimplementowane. Z artykułu IBM DeveloperWorks :

  • symetria: dla dwóch odwołań, a i b, A. równa się (b) wtedy i tylko wtedy, gdy B. równa się (a)
  • Refleksywność: dla wszystkich odniesień nie-null, A. równa się (a)
  • przechodniość: Jeśli a. równa się (b) i b. równa się (c), to a. równa się(C)

Następnie upewnij się, że ich relacja z hashCode respektuje kontakt (z tego samego artykułu):

  • spójność z hashCode (): dwa równe obiekty muszą mieć tę samą wartość hashCode ()

Wreszcie dobra funkcja hash powinna dążyć do osiągnięcia idealnej funkcji hash .

 17
Author: Grey Panther,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 07:08:10

About8.blogspot.com, powiedziałeś

Jeśli equals () zwraca true dla dwóch obiektów, to hashCode() powinien zwrócić tę samą wartość. Jeśli equals () zwróci false, wtedy hashCode () zwróci inne wartości

Nie mogę się z tobą zgodzić. Jeśli dwa obiekty mają ten sam hashcode, nie musi to oznaczać, że są równe.

Jeśli a jest równe B, to A. hashcode musi być równe B. hascode

Ale

Jeśli A. hashcode równa się B. hascode to nie znaczy, że a musi równa się B

 11
Author: Attila,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-31 13:49:37

Jest dobra implementacja efektywnej Javy 'S hashcode() i equals() logiki w Apache Commons Lang . Checkout HashCodeBuilder i EqualsBuilder.

 7
Author: Rudi Adianto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-07 17:30:40

Jeśli używasz eclipse, możesz wygenerować equals() i hashCode() używając:

Source - > Generate hashCode () and equals ().

Używając tej funkcji możesz zdecydować które pola chcesz użyć do obliczenia równości i skrótu, a Eclipse generuje odpowiednie metody.

 6
Author: Johannes K. Lehnert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-07 17:26:57

Tylko szybka notka do uzupełnienia innych bardziej szczegółowych odpowiedzi (w zakresie kodu):

Jeśli rozważę pytanie Jak-utworzyć-a-hash-table-w-Javie, a zwłaszcza wpis JGURU FAQ , uważam, że inne kryteria, według których można oceniać kod hashowy to:

  • synchronizacja (czy algo obsługuje dostęp współbieżny, czy nie) ?
  • Czy algo wykrywa zbiór, który zmienia się podczas iteracji?]}
  • wartość null (czy kod hash obsługuje wartość null w kolekcji)
 4
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:57

Jeśli dobrze rozumiem twoje pytanie, masz własną klasę collection (tj. nową klasę, która rozciąga się z interfejsu Collection) i chcesz zaimplementować metodę hashCode ().

Jeśli twoja klasa collection rozszerza AbstractList, to nie musisz się o to martwić, istnieje już implementacja equals() i hashCode (), która działa poprzez iterację wszystkich obiektów i dodawanie ich hashCodes () razem.

   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;
   }

Teraz, jeśli to, czego chcesz, jest najlepszym sposobem na Oblicz kod hash dla konkretnej klasy, zwykle używam operatora ^ (bitowo exclusive or) do przetwarzania wszystkich pól, których używam w metodzie equals:

public int hashCode(){
   return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
 4
Author: Mario Ortegón,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 07:16:46

@oout8: jest tam dość poważny błąd.

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

Ten sam hashcode

Prawdopodobnie chcesz coś takiego

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

(czy w dzisiejszych czasach można pobrać hashCode bezpośrednio z int w Javie? Myślę, że to trochę autocasting.. jeśli tak jest, pomiń toString, to jest brzydkie.)

 2
Author: SquareCog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 07:06:16

Ponieważ prosiłeś o Kolekcje, chciałbym dodać aspekt, o którym inne odpowiedzi jeszcze nie wspominały: HashMap nie oczekuje, że ich klucze zmienią hashcode po dodaniu ich do kolekcji. Zniweczyłoby to cały cel...

 2
Author: Olaf Kock,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 07:15:38

Użyj metod refleksyjnych na Apache Commons EqualsBuilder i HashCodeBuilder .

 2
Author: Vihung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-07 17:32:32

Każda metoda mieszania, która równomiernie rozkłada wartość mieszania w możliwym zakresie, jest dobrą implementacją. Zobacz skuteczne java ( 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), jest tam dobra wskazówka dla implementacji hashcode (chyba punkt 9...).

 1
Author: Chii,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 07:20:26

Wolę używać metod użytkowych fromm Google Collections lib z class Objects , które pomagają mi utrzymać mój kod w czystości. Bardzo czÄ ™ sto metody equals i hashcode sÄ ... wykonane z szablonu IDE, wiÄ ™ c nie sÄ ... czyste do odczytania.

 1
Author: nbro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-07 17:28:19

Używam malutkiego opakowania wokół Arrays.deepHashCode(...) ponieważ obsługuje tablice dostarczane jako parametry poprawnie

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}
 1
Author: starikoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-21 12:08:22

Oto kolejna demonstracja podejścia JDK 1.7+ z uwzględnieniem logiki superklasycznej. Widzę to jako dość wygodne z klasą obiektową hashCode (), czystą zależnością od JDK i bez dodatkowej pracy ręcznej. Należy pamiętać, że {[1] } jest tolerancyjne.

Nie uwzględniłem żadnej equals() implementacji, ale w rzeczywistości będzie ona oczywiście potrzebna.

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
Author: Roman Nikitchenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-30 13:18:51

Standardowa implementacja jest słaba i korzystanie z niej prowadzi do niepotrzebnych kolizji. Imagine a

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);
    }

    ...
}

Teraz,

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

I

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

Mają ten sam hashCode, a mianowicie {[4] } jako mnożnik używany dla List.hashCode jest tutaj ponownie używany. Oczywiście kolizje są nieuniknione, ale wytwarzanie niepotrzebnych kolizji jest po prostu... niepotrzebnie.

Nie ma nic mądrego w używaniu 31. Mnożnik musi być nieparzysty, aby uniknąć utraty informacji (każdy mnożnik parzysty traci przynajmniej najbardziej znaczący bit, wielokrotność czterech traci dwa, itp.). Każdy nieparzysty mnożnik jest użyteczny. Małe mnożniki mogą prowadzić do szybszych obliczeń( JIT może używać przesunięć i uzupełnień), ale biorąc pod uwagę, że mnożenie ma opóźnienie tylko trzech cykli na nowoczesnych Intel / AMD, to nie ma znaczenia. Małe mnożniki prowadzą również do większej kolizji dla małych wejść, co czasami może być problemem.

Używanie liczby pierwszej jest bezcelowe, ponieważ liczby pierwsze nie mają znaczenia w pierścieniu Z / (2 * * 32).

Więc, ja zalecamy użycie losowo wybranej dużej liczby nieparzystej (możesz wziąć liczbę pierwszą). Ponieważ procesory i86/amd64 mogą używać krótszej instrukcji dla operandów pasujących do jednego podpisanego bajtu, istnieje niewielka przewaga prędkości dla mnożników takich jak 109. Aby zminimalizować kolizje, weź coś w stylu 0x58a54cf5.

Używanie różnych mnożników w różnych miejscach jest pomocne, ale prawdopodobnie nie wystarcza, aby uzasadnić dodatkową pracę.

 1
Author: maaartinus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-10 18:02:05

Podczas łączenia wartości hash, zwykle używam metody łączenia, która jest używana w bibliotece boost c++, a mianowicie:

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

Jest to dość dobra praca, aby zapewnić równomierną dystrybucję. Aby dowiedzieć się, jak działa ta formuła, Zobacz post StackOverflow: Magiczna liczba w boost:: hash_combine

Jest dobre omówienie różnych funkcji hashowych na: http://burtleburtle.net/bob/hash/doobs.html

 0
Author: Edward Loper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:45

Dla prostej klasy często najłatwiej jest zaimplementować hashCode() w oparciu o pola klasy, które są sprawdzane przez implementację equals ().

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;
    }
}

Najważniejszą rzeczą jest zachowanie spójności hashCode() i equals (): jeśli equals() zwraca true dla dwóch obiektów, to hashCode() powinien zwrócić tę samą wartość. Jeśli equals () zwróci false, wtedy hashCode() zwróci inne wartości.

 -1
Author: Chris Carruthers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-22 07:28:31