Maison  >  Article  >  Java  >  Pourquoi Java doit-il réécrire le hashcode lors de la réécriture d'égaux ?

Pourquoi Java doit-il réécrire le hashcode lors de la réécriture d'égaux ?

王林
王林avant
2021-01-08 10:22:332718parcourir

Pourquoi Java doit-il réécrire le hashcode lors de la réécriture d'égaux ?

Tout d'abord, laissez-moi vous dire la conclusion :

Il faut d'abord préciser que réécrire des égaux ne nécessite pas nécessairement de hashcode, cela dépend de la situation réelle . Par exemple, cela n'est pas nécessaire lorsqu'un conteneur n'est pas utilisé, mais si un conteneur tel que HashMap est utilisé et qu'un objet personnalisé est utilisé comme clé, il doit être réécrit.

(Partage vidéo d'apprentissage : tutoriel vidéo Java)

Réécrire des égaux consiste à déterminer si les instances sont égales dans la logique métier. Le but de la réécriture du hascode est de déterminer rapidement le poids de la collection.

Dispositions pour hashCode() et equals() :

1 Si deux objets sont égaux, le hashcode doit également être le même
2. () renvoie true
3. Deux objets ont la même valeur de hashcode, et ils ne sont pas nécessairement égaux
4 En résumé, la méthode equals() a été remplacée, donc la méthode hashCode() doit également l'être. Remplacez
5. Le comportement par défaut de hashCode() est de produire des valeurs uniques pour les objets sur le tas. Si hashCode() n'est pas remplacé, de toute façon, deux objets de cette classe ne seront pas égaux (même si les deux objets pointent vers les mêmes données).

Ce qui suit est un exemple pour illustrer la nécessité de réécrire.

Lors de l'utilisation d'une classe personnalisée comme clé de HashMap lors de la mise

Si vous réécrivez uniquement égal mais pas hashCode, une erreur logique se produira

Regardez d'abord le code suivant

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

Exécuter le résultat :

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

a réécrit la méthode equals dans le code, mais n'a pas réécrit la méthode hashCode.
La logique de la réécriture égale est la suivante : tant que le orderId est égal, les deux objets sont égaux.
À en juger par les résultats d'exécution, les deux objets avec le même orderId ont été placés avec succès dans la carte. Il s'agit d'une erreur logique, car logiquement, le résultat attendu ne devrait être qu'un seul ordre dans la carte.
Jetons un coup d'œil au code source de HashMap
Regardez simplement le jugement avec les commentaires

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

Nous savons d'après le code source que tant que le code de hachage est différent, cela peut être directement inséré dans le tableau. Cependant, précisément parce que nous n’avons pas remplacé la méthode hashCode, la méthode hashCode de Object est appelée. Le hashCode of Object utilise l'adresse de l'objet dans le tas pour dériver une valeur de type int via un algorithme. Dans ce cas, les valeurs de type int des deux objets qui viennent d'être créés doivent être différentes, donc les deux ordres peuvent l'être. inséré normalement dans le tableau, ce qui entraîne une erreur logique.

Réécrivez la méthode hashCode :

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

Exécutez à nouveau la sortie :

{Order{orderId=1000000001}=}

Jetons un bref coup d'œil au code source (pour une meilleure compréhension, j'ai seulement intercepté le code clé ): Put order2 Expliqué sous forme de commentaire.

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

Donc, order2 écrase order1. C'est pourquoi lorsque vous utilisez un objet personnalisé comme clé d'un HashMap, si vous remplacez égal à, vous devez également hashCode.

D'un autre côté : après avoir réécrit hashCode, faut-il réécrire égal ?

La réponse est oui, il faut tout réécrire !

Prenons comme exemple la logique de réécriture du code ci-dessus. Supposons que deux objets ont le même hashCode et que order1 a été mis, le hachage est le même et l'index résultant est également le. De même, alors order1 peut être obtenu. Après cela, la comparaison égale continuera à être utilisée. En supposant qu'il n'y ait pas de réécriture, ce sera une comparaison d'adresse d'objet. Le résultat doit alors être faux. une liste chaînée sera formée.

Également dans map.get(key), il sera recherché en fonction du hashCode puis les égaux seront déterminés.
Pourquoi devons-nous juger sur un pied d’égalité ? Étant donné que ce qui est trouvé sur la base de hashCode est une liste chaînée, vous devez trouver la valeur avec une clé égale dans la liste chaînée basée sur des égaux.

Quels scénarios utiliseraient des classes personnalisées comme clés ?

La clé la plus courante est une coordonnée, comme placer un objet à une certaine coordonnée sur la carte.

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), "电脑");
    }
}

Recommandations associées : Tutoriel d'introduction à Java

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer