有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么在添加HashSet和hashCode匹配项时不调用equals()?

当我运行这段代码时,为什么只调用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;  
    }
}

其输出为:

Hello-hashcode
Hello-hashcode
Ho size=1

共 (5) 个答案

  1. # 1 楼答案

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

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

  2. # 2 楼答案

    哈希集首先检查引用相等性,如果通过,它将跳过.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;
    }
    
  3. # 3 楼答案

    AHashSet使用HashMap作为集合的支持机制。通常,我们希望调用hashCodeequals以确保没有重复项。然而,put方法(调用private{}方法来执行实际操作)在源代码中进行了优化:

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

    如果散列码匹配,它在调用equals之前首先检查密钥是否相同。您正在传递相同的Student对象,因此它们已经是==,因此||操作符短路,并且equals永远不会被调用

    如果传入了另一个Student对象,但具有相同的ID,那么==将返回false,并且equals将被调用

  4. # 4 楼答案

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

  5. # 5 楼答案

    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.

    hashcode() and equals() method