Почему 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
Ответы
Ответ 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 никогда не вызывается.