Hashset的HashCode和Equals

47
请帮我澄清一下关于Hashset的疑惑。考虑以下代码:
class Person
{
    String name;

    Person(String n)
    {
        name=n; 
    }
    public String getName()
    {
        return name;   
    }

    @Override
    public boolean equals(Object arg0) {

        System.out.println("in equals");

        Person obj=(Person)arg0;

        System.out.println("1st "+getName());
        System.out.println("2nd "+obj.getName());

        if(this.getName().equals(obj.getName()))
        {
                return true;
        }
        return false;
    }


    @Override
    public int hashCode() {

        System.out.println("in hash code");
        System.out.println(" value is "+Integer.valueOf(name.charAt(0)));
        return Integer.valueOf(name.charAt(0));
    }
}

在主函数中,我有以下代码

Person obj1=new Person("bcd");

Person obj2=new Person("cde");

Person obj3=new Person("abc");

Person obj4=new Person("abc");

现在,如果我将这些对象添加到哈希集合中

Set<Person> sset=new HashSet<Person>();

sset.add(obj1);
sset.add(obj4);
sset.add(obj2);
sset.add(obj3);
我得到了这个输出
in hash code                                                                      
value is 98    
in hash code   
value is 97    
in hash code    
value is 99    
in hash code    
value is 97  
in equals  
1st abc     
2nd abc

问题1:为什么equals()函数只被调用了一次来检查obj3和obj4?为什么没有为其他对象检查?

问题2:如果答案是因为它们都有相同的哈希码,那么只有在equals被调用时才会被检查,那么为什么下面的代码不会调用equals函数:

sset.add(obj1);
sset.add(obj4);
sset.add(obj2);
sset.add(obj4);

输出结果为:

in hash code  
value is 98  
in hash code   
value is 97   
in hash code   
value is 99   
in hash code  
value is 97 

即使将两个相同的对象添加到具有相同哈希码的哈希集中,它也不会进入equals()方法。

问题3:我遍历了上面的值并打印了内容,但是没有调用hashcode或equals。什么时候真正有用地覆盖hashCode和equals方法?

问题4:什么时候会调用hashCode()equals()方法?


3
嗯...我应该阅读这个问题,但是没有代码格式?...我自己格式化它以理解它吗?...不,太晚了,我回家了。晚安。 - Daniel
@Paŭlo Ebermann:请不要在编辑时改变答案的含义 - 它是有意使用“hashCode”而不是“equals”来表达的。如果您不同意我的回答,可以自由地编写新的回答。 - Erik
@Erik:抱歉,因为问题是关于调用.equals的,所以我认为答案也应该和这个有关。 (是的,你是对的,我应该添加一个注释而不是直接修改答案。) - Paŭlo Ebermann
5个回答

59
  1. 如果hashCode不同,则无需调用equals
  2. 如果(obj1 == obj2),则无需调用hashCode
  3. 仅需要迭代时无需使用hashCode和/或equals - 您不需要比较对象。
  4. 在需要区分对象时使用。

4
HashMap 实际上会计算哈希值以找到正确的桶(bucket),并在使用之前通过比较对象标识符 == 或调用 equals() 来确定对象是否相等,但除此之外这段话是正确的。第二个示例中没有调用 equals() 因为 == 已经检测到了重复项。 - David Harkness
5
仅仅为了迭代,并不需要使用hashCode和/或equals方法 - 因为你不会比较对象---那么在迭代中如果不调用hashcode方法,它将如何获取当前的桶(bucket)呢? - Pramod Kumar

20

如果您了解集合,尤其是哈希集合(HashSets)的工作原理,那么您所有的问题都将得到解答。一个集合是一组唯一对象,Java定义唯一性的方式是它不等于任何其他对象(equals返回false)。

HashSet利用哈希码来加速操作。它假定两个相等的对象将具有相同的哈希码。但是,它不会假定具有相同哈希码的两个对象是相等的。这就是为什么当它检测到哈希码冲突时,会仅与集合中具有相同哈希码的其他对象(在您的情况下只有一个)进行比较。


12
根据 javasourcecode.org 上的 JDK 源代码,HashSet 使用 HashMap 作为其内部实现,关于 HashSet 的 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方法。


2

因为在第二种情况下,您添加了相同的引用两次,而 HashSet 在其基于 HashMap.put() 的方法中对此进行了检查:

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }

正如您所看到的,只有在要添加的键的哈希值等于已经存在于集合中的键 并且 这两个键的引用不同的情况下,才会调用equals方法。


2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接