Heim >Java >JavaErste Schritte >Warum muss Java den Hashcode neu schreiben, wenn das Umschreiben gleich ist?

Warum muss Java den Hashcode neu schreiben, wenn das Umschreiben gleich ist?

王林
王林nach vorne
2021-01-08 10:22:332745Durchsuche

Warum muss Java den Hashcode neu schreiben, wenn das Umschreiben gleich ist?

Lassen Sie mich zunächst die Schlussfolgerung sagen:

Wir müssen zunächst klarstellen, dass das Umschreiben von Gleichheit nicht unbedingt einen Hashcode erfordert, sondern von der tatsächlichen Situation abhängt. Dies ist beispielsweise nicht erforderlich, wenn kein Container verwendet wird. Wenn jedoch ein Container wie HashMap verwendet wird und ein benutzerdefiniertes Objekt als Schlüssel verwendet wird, muss es neu geschrieben werden.

(Lernvideo-Sharing: Java-Video-Tutorial)

Das Umschreiben von equal dient dazu, festzustellen, ob Instanzen in der Geschäftslogik gleich sind. Der Zweck des Umschreibens von Hascode besteht darin, das Gewicht der Sammlung schnell zu bestimmen.

HashCode()- und equal()-Regeln:

1. Wenn zwei Objekte gleich sind, muss auch der Hashcode gleich sein
2 Wenn zwei Objekte gleich sind, gibt die equal()-Methode für beide „true“ zurück Objekte haben den gleichen Hashcode-Wert und sind nicht unbedingt gleich.
4 Zusammenfassend lässt sich sagen, dass die Methode hashCode() auch überschrieben werden muss Das Objekt erzeugt einen eindeutigen Wert. Wenn hashCode() nicht überschrieben wird, sind ohnehin keine zwei Objekte dieser Klasse gleich (auch wenn die beiden Objekte auf dieselben Daten zeigen).

Das Folgende ist ein Beispiel, um die Notwendigkeit des Umschreibens zu veranschaulichen.

Wenn beim Einfügen eine benutzerdefinierte Klasse als HashMap-Schlüssel verwendet wird

Wenn Sie nur equal umschreiben, ohne hashCode neu zu schreiben, tritt ein Logikfehler auf

Schauen Sie sich zuerst den folgenden Code an

public class Test {

    static class Order {
    
        private Long orderId;

        public Order(Long orderId) {
            this.orderId = orderId;
        }

        public Long getOrderId() {
            return orderId;
        }

        public void setOrderId(Long orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj != null && !(obj instanceof Order)) {
                return false;
            }

            return Objects.equals(this.orderId, ((Order) obj).orderId);
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    '}';
        }
    }

    public static void main(String[] args) {
        Map<Order, String> map = new HashMap<>();

        Order order1 = new Order(1000000001L);
        Order order2 = new Order(1000000001L);

        map.put(order1, "");
        map.put(order2, "");

        System.out.println(map);
    }
}

Führen Sie die Ausgabe aus:

{Order{orderId=1000000001}=, Order{orderId=1000000001}=}

Umschreiben Der Code Die Equals-Methode wurde ersetzt und die HashCode-Methode wurde nicht überschrieben. Die Logik des Umschreibens von

equals lautet: Solange die Bestell-ID gleich ist, sind die beiden Objekte gleich.

Den laufenden Ergebnissen zufolge wurden die beiden Objekte mit derselben Bestell-ID erfolgreich in die Karte eingefügt. Dies ist ein logischer Fehler, da das erwartete Ergebnis logischerweise nur eine Bestellung in der Karte sein sollte.
Werfen wir einen Blick auf den Quellcode von HashMap
Schauen Sie sich einfach das Urteil mit Kommentaren an

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
     int h;
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 通过hash算出索引  通过索引取值==null的话  直接直接插入到索引位置。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

Aus dem Quellcode wissen wir, dass der Hash-Code, solange er unterschiedlich ist, direkt in das Array eingefügt werden kann. Doch gerade weil wir die hashCode-Methode nicht überschrieben haben, wird die hashCode-Methode von Object aufgerufen. Der HashCode des Objekts verwendet die Adresse des Objekts im Heap, um über einen Algorithmus einen int-Typwert abzuleiten. In diesem Fall müssen die int-Typwerte der beiden gerade erstellten Objekte unterschiedlich sein, sodass beide Aufträge möglich sind normal in das Array eingefügt, was zu einem Logikfehler führt.

Schreiben Sie die HashCode-Methode neu:

public class TestHash {

    static class Order {


        private Long orderId;

        public Order(Long orderId) {
            this.orderId = orderId;
        }

        public Long getOrderId() {
            return orderId;
        }

        public void setOrderId(Long orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj != null && !(obj instanceof Order)) {
                return false;
            }

            return Objects.equals(this.orderId, ((Order) obj).orderId);
        }

        @Override
        public int hashCode() {
        	// 这里简单重写下   实际开发根据自己需求重写即可。
            return this.orderId.intValue() >> 2;
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId=" + orderId +
                    &#39;}&#39;;
        }
    }

    public static void main(String[] args) {
        Map<Order, String> map = new HashMap<>();

        Order order1 = new Order(1000000001L);
        Order order2 = new Order(1000000001L);

        map.put(order1, "");
        map.put(order2, "");

        System.out.println(map);
    }
}

Führen Sie die Ausgabe erneut aus:

{Order{orderId=1000000001}=}

Werfen wir einen kurzen Blick auf den Quellcode (zum besseren Verständnis habe ich nur den Schlüsselcode abgefangen): Verwenden Sie put order2 als Kommentar zur Erläuterung.

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 重写hashCode之后两个对象的orderId相同,hashCode也肯定相同。
    // 通过hash算出索引  通过索引取值  有值不进入if。
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        // 由于重写了hashCode  旧对象的hashCode和新的肯定相等
        if (p.hash == hash &&
        // (k = p.key) == key == false 因为比较的是对象地址
        // (key != null && key.equals(k)) == true 因为重写了equals orderId相等则相等 
            ((k = p.key) == key || (key != null && key.equals(k))))
            // 保存旧Node
            e = p;
        .......
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
            	// value覆盖旧Node的值
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
   ........
}

Bestellung2 deckt also Bestellung1 ab. Wenn Sie also ein benutzerdefiniertes Objekt als Schlüssel einer HashMap verwenden und „equals“ überschreiben, müssen Sie auch hashCode verwenden.

Umgekehrt: Muss equal nach dem Umschreiben von hashCode neu geschrieben werden?

Die Antwort ist ja, alles muss neu geschrieben werden!

Nehmen wir als Beispiel die Logik des Umschreibens des obigen Codes und nehmen Sie an, dass beim Einfügen zwei Objekte mit demselben Hash-Code vorhanden sind und dass der erhaltene Index derselbe ist. Sie können order1 abrufen und nach dem Abrufen mit „equals“ fortfahren. Vorausgesetzt, es erfolgt kein Umschreiben, dann handelt es sich um einen Objektadressenvergleich, das Ergebnis muss falsch sein, dann tritt zu diesem Zeitpunkt eine Hash-Kollision auf und es wird eine verknüpfte Liste erstellt .

Auch in map.get(key) wird es basierend auf hashCode durchsucht und dann wird gleich beurteilt.

Warum müssen wir auf Augenhöhe urteilen? Da es sich bei dem, was auf der Grundlage von HashCode gefunden wird, um eine verknüpfte Liste handelt, müssen Sie den Wert mit dem gleichen Schlüssel in der auf „equals“ basierenden verknüpften Liste finden.


In welchen Szenarien würden benutzerdefinierte Klassen als Schlüssel verwendet?

Der häufigste Schlüssel ist eine Koordinate, beispielsweise die Platzierung eines Objekts an einer bestimmten Koordinate auf der Karte.

public class Test {

    static class Coordinate {
        public Coordinate(int x, int y) {
            this.x = x;
            this.y = y;
        }

        private int x;
        private int y;

        public int getX() {
            return x;
        }

        public void setX(int x) {
            this.x = x;
        }

        public int getY() {
            return y;
        }

        public void setY(int y) {
            this.y = y;
        }
    }

    public static void main(String[] args) {
        Map<Coordinate, String> map = new HashMap<>();
        map.put(new Coordinate(22, 99), "手机");
        map.put(new Coordinate(44, 48), "电脑");
    }
}

Verwandte Empfehlungen:

Java-Einführungs-Tutorial

Das obige ist der detaillierte Inhalt vonWarum muss Java den Hashcode neu schreiben, wenn das Umschreiben gleich ist?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:csdn.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen