Подтвердить что ты не робот

Почему equals() не вызывается при добавлении в HashSet и hashCode совпадений?

Когда я запускаю этот код, почему только hashCode() вызывается не методом equals, тогда как моя реализация hashCode() генерирует те же hashCode для обеих записей в HashSet?

import java.util.HashSet;

public class Test1 {
    public static void main(String[] args) {
        Student st=new Student(89);
        HashSet st1=new HashSet();
        st1.add(st);
        st1.add(st);
        System.out.println("Ho size="+st1.size());
    }
}
class Student{
    private int name;
    private int ID;
    public Student(int iD) {
        super();
        this.ID = iD;
    }
    @Override
    public int hashCode() {
        System.out.println("Hello-hashcode");
        return ID;
    }
    @Override
    public boolean equals(Object obj) {
        System.out.println("Hello-equals");
        if(obj instanceof Student){
            if(this.ID==((Student)obj).ID){
                return true;
            }
            else{
                return false;
            }
        }
        return false;  
    }
}

Выход для этого:

Hello-hashcode
Hello-hashcode
Ho size=1
4b9b3361

Ответ 1

Набор хешей сначала проверяет ссылочное равенство, и если он проходит, он пропускает вызов .equals. Это оптимизация и работает, потому что контракт equals указывает, что если a == b, то a.equals(b).

Я прикрепил исходный код ниже, с этой проверкой.

Если вы вместо этого добавите два равных элемента, которые не являются одной и той же ссылкой, вы получите эффект, который ожидаете:

    HashSet st1=new HashSet();
    st1.add(new Student(89));
    st1.add(new Student(89));
    System.out.println("Ho size="+st1.size());

приводит к

$ java Test1
Hello-hashcode
Hello-hashcode
Hello-equals
Ho size=1

Здесь исходный код OpenJDK 7 с оптимизацией оптимизации (из HashMap, базовой реализации HashSet):

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
//                                         v-- HERE
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

Ответ 2

A HashSet использует HashMap в качестве своего механизма поддержки для набора. Обычно мы ожидаем, что hashCode и equals будут вызваны для обеспечения отсутствия дубликатов. Однако метод put (который вызывает метод private putVal для выполнения фактической операции) делает оптимизацию в исходном коде:

if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))

Если хэш-коды совпадают, сначала он проверяет, совпадают ли ключи до вызова equals. Вы передаете один и тот же объект Student, поэтому они уже ==, поэтому короткие замыкания оператора || и equals никогда не вызываются.

Если вы передали другой объект Student, но с тем же ID, то == вернет false и equals будет вызван.

Ответ 3

Равенства всегда вызывается после метода hashCode в хэши java при добавлении и удалении элементов. Причина в том, что если есть элемент уже в указанном ведре, тогда проверка JVM будь то тот же самый элемент, который он пытается поставить.

hashcode() и метод equals()

Ответ 4

Если два объекта равны в соответствии с методом equals (Object), то вызов метода hashCode для каждого из двух объектов должен давать одинаковый результат целых чисел.

Ответ 5

Просматривая исходный код HashSet, он использует HashMap для всех своих операций, а метод add выполняет put(element, SOME_CONSTANT_OBJECT). Вот исходный код метода put для JDK 1.6.0_17:

public V put(K key, V value) {
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    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;
            e.recordAccess(this);
            return oldValue;
        }
    }

    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

как вы можете видеть, он выполняет сравнение == перед использованием метода equals. Поскольку вы добавляете один и тот же экземпляр объекта дважды, == возвращает true, а метод equals никогда не вызывается.