Почему объекту в HashMap нужно переписать equals и hashcode как Key

Java

Медовый сок равен и хэш-код

Ха-ха! У меня разборка! Интервьюеры Ali и Meituan задавали мне один и тот же вопрос: вы уверены, что не понимаете?

Сегодня поговорим о чем-то простом, что есть в初级开发及校招В интервью часто задают вопрос.

  • На что следует обратить внимание, когда ключ HashMap является объектом?

  • Зачем одновременно переопределять методы equals и hashcode?

Даю тебе десять секунд, чтобы подумать, как тебе ответить... ⌚

Неважно, если вы не можете об этом подумать, после прочтения этой статьи я столкнулся с той же проблемой в интервью.送分题. 📕

Что такое методы equals и hashcode

Мы знаем, что все классы в Java наследуются от класса Object, а класс Object является родительским классом для всех классов. Когда подкласс вызывает метод, если этот метод не был переопределен, ему необходимо найти метод в суперклассе для выполнения.

Класс объекта

 public boolean equals(Object obj) {
        return (this == obj);
 }
 public native int hashCode();

В классе Object hashCode — это локальный метод, который просто понимается как获取对象地址, метод equals сравнивает себя с obj对象地址равны. Здесь вы должны сначала распознать эти два метода, один — взять адрес, другой — сравнить адрес.


Введите тему ниже...

Что происходит, когда объект является ключом

Ниже приведен небольшой код для демонстрации, выводnull. Позвольте мне сначала сказать кое-что: здесь мы думаем, что мои a и b являются одним и тем же объектом (ключом) с одинаковыми свойствами, и я могу получить строку hello через hashMap.get(b). Но это имело неприятные последствия.

public class NoHashCodeAndEquals {
    public static void main(String[] args) {
        Object o = new Object();
        HashMap<Demo, String> hashMap = new HashMap<>();
        Demo a = new Demo("A");
        Demo b = new Demo("A");
        hashMap.put(a, "hello");
        String s = hashMap.get(b);
        System.out.println(s);

    }
}
class Demo {
    String key;

    Demo(String key) {
        this.key = key;
    }
}

Может быть, каждый увидит это с первого взгляда и почувствует, что проблемы нет, a и b — не один и тот же объект! Адрес точно другой! Останавливаться! О чем ты говоришь, что я только что сказал? 👆 Запутался! 😵 (пс: я сама почти в кругу🤭).

Подожди! ! !

Давайте просто посмотрим на две строки исходного кода HashMap и сразу проснемся после прочтения.

  • Получите хэш-код для вычисления нижнего индекса корзины и сохранения элемента.Кажется, в вычислении нижнего индекса нет ничего плохого. правильно! Просто вызовите хэш-код ключа, чтобы вычислить значение индекса.
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

Видя метод hashCode, нам все еще не хватает метода equals.Где вызов метода equals() в HashMap?

  • метод put (в качестве примера возьмем JDK1.7)
public V put(K key, V value) {
...
int hash = hash(key);
// 确定桶下标
int i = indexFor(hash, table.length);
// 先找出是否已经存在键为 key 的键值对,如果存在的话就更新这个键值对的值为 value
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
return oldValue;
}
}
...
// 插入新键值对
addEntry(hash, key, value, i)
return null;
}
  • метод get (в качестве примера возьмем JDK1.7)
public V get(Object key) {  
    if (key == null)  
        return getForNullKey();  
    int hash = hash(key.hashCode());  
    for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {  
        Object k;  
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
            return e.value;  
    }  
    return null;  
}

Как показано выше: Когда HashMap вызывает метод вставки или получения, необходимо сравнить хэш-код, соответствующий значению (ключу), с хэш-кодом в массиве, Если они равны, метод equals будет использоваться для сравнения значения ключей равны.

В сочетании с кодовым случаем, который я написал выше, давайте воспользуемся моментом и попробуем его снова!

Demo a = new Demo("A");
Demo b = new Demo("A");

Смысл этих двух строк кода мы понимаем, что он определяет два相同含义(думая, что это один и тот же ключ) ключевой объект, но все знают, что значение метода хэш-кода этих двух ключей различно.

Ключ сравнения в HashMap такой, сначала находим hashcode() ключа, сравниваем, равны ли его значения, а потом сравниваем equals(), если они равны, если они равны, они считаются равными. Если equals() не равно, то они не считаются равными.

  • Если переопределен только метод hashcode(), а метод equals() не переопределен, при сравнении equals() фактически вызывается метод в Object, просто чтобы увидеть, являются ли они одним и тем же объектом (т.е. сравнить адреса памяти) .
  • Если вы переопределяете только equals() без переопределения метода hashcode(), HashMap будет заблокирован при принятии решения и будет рассматриваться как другой ключ.

Итак, я хочу использовать объект в качестве ключа HashMap,Вы должны переопределить методы hashCode и equals объекта.. Убедитесь, что значение equals также истинно, когда hashCode равен.

  • Схема выглядит следующим образом:

Эта задача кажется простой, но многие младшие программисты не могут сказать почему: с одной стороны, они не знакомы с HashMap, а с другой стороны, не до конца понимают смысл этих двух методов.

Наконец, снова резюмируем: храните пользовательские объекты в «ключевой» части HashMap,一定переписатьequalsиhashCodeметод. Еще два клише!

  • Если два объекта == равны, их хэш-коды должны быть равны, и наоборот.
  • Если равны два объекта равны, их хэш-коды должны быть равны, и наоборот не обязательно верно.

Как насчет того, чтобы снова выпить его самому~