为什么在添加HashSet和hashCode匹配时没有调用equals()?

时间:2022-02-25 21:50:10

When I run this code why only hashCode() is called not equals method while my hashCode() implementation generate same hashCode for both entries to HashSet?

当我运行此代码时,为什么只调用hashCode()而不是equals方法,而我的hashCode()实现为两个HashSet条目生成相同的hashCode?

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

The output for this is:

这个输出是:

Hello-hashcode
Hello-hashcode
Ho size=1

5 个解决方案

#1


24  

The hash set checks reference equality first, and if that passes, it skips the .equals call. This is an optimization and works because the contract of equals specifies that if a == b then a.equals(b).

哈希集首先检查引用相等性,如果超过,则跳过.equals调用。这是一个优化并且有效,因为equals的契约指定如果a == b则a.equals(b)。

I attached the source code below, with this check highlighted.

我附上了下面的源代码,突出显示了此检查。

If you instead add two equal elements that are not the same reference, you get the effect you were expecting:

如果你改为添加两个相同的参考元素,你会得到你期望的效果:

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

results in

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

Here's the source code from OpenJDK 7, with equality optimization indicated (from HashMap, the underlying implementation of HashSet):

这是来自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


9  

A HashSet uses a HashMap as its backing mechanism for the set. Normally, we would expect that hashCode and equals would be called to ensure that there are no duplicates. However, the put method (which calls a private putVal method to do the actual operation) makes an optimization in the source code:

HashSet使用HashMap作为集合的支持机制。通常,我们希望调用hashCode和equals以确保没有重复项。但是,put方法(调用私有putVal方法来执行实际操作)在源代码中进行优化:

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

If the hash codes match, it first checks to see if the keys are the same before calling equals. You are passing the same Student object, so they are already ==, so the || operator short-circuits, and equals is never called.

如果哈希码匹配,它首先检查在调用equals之前键是否相同。您传递的是同一个Student对象,因此它们已经是==,所以||运算符短路,从不调用equals。

If you passed in a different Student object but with the same ID, then == would return false and equals would be called.

如果传入了不同的Student对象但具有相同的ID,则==将返回false并且将调用equals。

#3


3  

Equals is always called after the hashCode method in a java hashed collection while adding and removing elements. The reason being, if there is an element already at the specified bucket, then JVM checks whether it is the same element which it is trying to put.

在添加和删除元素时,始终在java哈希集合中的hashCode方法之后调用Equals。原因是,如果某个元素已经存在于指定的存储桶中,那么JVM会检查它是否与它尝试放置的元素相同。

hashcode() and equals() method

hashcode()和equals()方法

#4


3  

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果。

#5


3  

Looking through the source code of HashSet it is using HashMap for all of its operations and the add method performs put(element, SOME_CONSTANT_OBJECT). Here is the source code for the put method for JDK 1.6.0_17 :

查看HashSet的源代码,它使用HashMap进行所有操作,add方法执行put(element,SOME_CONSTANT_OBJECT)。以下是JDK 1.6.0_17的put方法的源代码:

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

as you can see it performs a == comparison before using the equals method. Since you are adding the same instance of an object twice the == returns true and the equals method is never called.

正如您所看到的,它在使用equals方法之前执行了==比较。由于您要添加两次对象的相同实例,因此==返回true,并且永远不会调用equals方法。

#1


24  

The hash set checks reference equality first, and if that passes, it skips the .equals call. This is an optimization and works because the contract of equals specifies that if a == b then a.equals(b).

哈希集首先检查引用相等性,如果超过,则跳过.equals调用。这是一个优化并且有效,因为equals的契约指定如果a == b则a.equals(b)。

I attached the source code below, with this check highlighted.

我附上了下面的源代码,突出显示了此检查。

If you instead add two equal elements that are not the same reference, you get the effect you were expecting:

如果你改为添加两个相同的参考元素,你会得到你期望的效果:

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

results in

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

Here's the source code from OpenJDK 7, with equality optimization indicated (from HashMap, the underlying implementation of HashSet):

这是来自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


9  

A HashSet uses a HashMap as its backing mechanism for the set. Normally, we would expect that hashCode and equals would be called to ensure that there are no duplicates. However, the put method (which calls a private putVal method to do the actual operation) makes an optimization in the source code:

HashSet使用HashMap作为集合的支持机制。通常,我们希望调用hashCode和equals以确保没有重复项。但是,put方法(调用私有putVal方法来执行实际操作)在源代码中进行优化:

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

If the hash codes match, it first checks to see if the keys are the same before calling equals. You are passing the same Student object, so they are already ==, so the || operator short-circuits, and equals is never called.

如果哈希码匹配,它首先检查在调用equals之前键是否相同。您传递的是同一个Student对象,因此它们已经是==,所以||运算符短路,从不调用equals。

If you passed in a different Student object but with the same ID, then == would return false and equals would be called.

如果传入了不同的Student对象但具有相同的ID,则==将返回false并且将调用equals。

#3


3  

Equals is always called after the hashCode method in a java hashed collection while adding and removing elements. The reason being, if there is an element already at the specified bucket, then JVM checks whether it is the same element which it is trying to put.

在添加和删除元素时,始终在java哈希集合中的hashCode方法之后调用Equals。原因是,如果某个元素已经存在于指定的存储桶中,那么JVM会检查它是否与它尝试放置的元素相同。

hashcode() and equals() method

hashcode()和equals()方法

#4


3  

If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果。

#5


3  

Looking through the source code of HashSet it is using HashMap for all of its operations and the add method performs put(element, SOME_CONSTANT_OBJECT). Here is the source code for the put method for JDK 1.6.0_17 :

查看HashSet的源代码,它使用HashMap进行所有操作,add方法执行put(element,SOME_CONSTANT_OBJECT)。以下是JDK 1.6.0_17的put方法的源代码:

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

as you can see it performs a == comparison before using the equals method. Since you are adding the same instance of an object twice the == returns true and the equals method is never called.

正如您所看到的,它在使用equals方法之前执行了==比较。由于您要添加两次对象的相同实例,因此==返回true,并且永远不会调用equals方法。