【问题标题】:Why is equals() not called while adding to HashSet and hashCode matches?为什么在添加到 HashSet 和 hashCode 匹配时不调用 equals()?
【发布时间】:2014-08-15 18:20:02
【问题描述】:

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

【问题讨论】:

  • hashcode() and equals() method 的可能重复项
  • 您是否有意不重写 hashCode() 方法?
  • @Vishrant - 这不是重复的 - “为什么不能调用 equals”部分未包含在链接的问题中。
  • san9w,我已经编辑了标题 - 看看你是否还好/恢复/改进。

标签: java


【解决方案1】:

哈希集首先检查引用相等性,如果通过,它会跳过.equals 调用。这是一种优化并且有效,因为equals 的合约指定如果a == ba.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】:

HashSet 使用HashMap 作为集合的支持机制。通常,我们希望调用hashCodeequals 以确保没有重复。但是put方法(调用privateputVal方法进行实际操作)在源码中做了优化:

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

如果哈希码匹配,它首先检查键是否相同,然后再调用equals。您正在传递相同的 Student 对象,因此它们已经是 ==,因此 || 运算符短路,并且永远不会调用 equals

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

【讨论】:

    【解决方案3】:

    查看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 方法。

    【讨论】:

      【解决方案4】:

      Equals 总是在 java hashed 中的 hashCode 方法之后调用 添加和删​​除元素时的集合。原因是,如果 指定桶中已有元素,然后JVM检查 它是否与它试图放置的元素相同。

      hashcode() and equals() method

      【讨论】:

      • 那么java api不正确吗? From 'add': "...如果这个集合不包含元素 e2 使得 (e==null ? e2==null : e.equals(e2)),则将指定的元素 e 添加到这个集合中。"
      • ? 'Hello equals' 不会在 OP 的代码中被调用。作为一个答案,这不应该解释为什么吗?
      • 您没有调用 .equals,因为引用检查表明对象引用是相同的,因此无需运行逻辑相等检查。
      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 2017-11-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        相关资源
        最近更新 更多