HashSet如何处理hashCode()方法?

11

我正在尝试更深入地了解java.util.Collection和java.util.Map,但是我对HashSet的功能有些疑问:

在文档中,它说:该类实现了Set接口,由哈希表(实际上是HashMap实例)支持。好的,所以我可以看到HashSet始终有一个Hashtable在后台工作。 Hashtable是一种结构,每当您想要向其中添加新元素时都会请求一个键和一个值。然后,根据键hashCode将值和键存储在一个桶中。如果两个键的hashcode相同,则会将两个键值添加到同一个桶中,使用链接列表。如果我说错了,请纠正我。

因此,我的问题是:如果HashSet始终具有在后台运行的Hashtable,那么每次我们使用HashSet.add()方法向HashSet添加新元素时,HashSet都应将其添加到其内部Hashtable中。但是,Hashtable要求一个值和一个键,那么它使用哪个键呢?它是否只使用我们尝试添加的值作为键,然后获取其hashCode?请纠正我有关HashSet实现的错误描述。

我另外一个问题是:通常哪些类可以使用java对象的hashCode()方法?我之所以问这个问题,是因为在文档中指出每次我们重写equals()方法时都需要重写hashCode()方法。好的,这确实是有道理的,但我的疑问是是否只是建议我们这样做以保持一切“良好和完美”(用这种方式表达),还是真的必要,因为可能许多Java默认类将经常使用您的对象的hashCode()方法。在我的看法中,除了与集合相关的那些类之外,我看不到其他类使用此方法。非常感谢大家。


你添加到集合中的“values”实际上是映射中的keysHashSet放入映射中的值是无关紧要的。它实际使用的值是一个名为PRESENT的对象的虚拟对象引用。 - ajb
2个回答

14

如果您查看HashSet的实际Java代码,您可以看到它做了什么:

 // Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
...

 public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

因此,您要添加的元素是作为后备哈希映射中的键,其值为虚拟值。 这个虚拟值实际上从未被哈希集使用。

关于覆盖equals和hashcode的第二个问题:

如果您想重写其中任何一个,则始终有必要同时覆盖它们两个。这是因为hashCode的契约规定相等的对象必须具有相同的哈希码。 hashCode的默认实现将为每个实例提供不同的值。

因此,如果您只覆盖equals()而不覆盖hashcode(),则可能会出现这种情况。

object1.equals(object2) //true

MySet.add(object1);

MySet.contains(object2); //false but should be true if we overrode hashcode()

由于contains方法使用哈希码来查找所在的桶,因此我们可能会得到一个不同的桶,并且无法找到相等的对象。


非常感谢!所以,根据您所说的,如果我们添加到HashSet中的值被用作其内部HashMap中的键,那么每次创建特定类型对象的HashSet时,如果我们想要获得最大性能,则应该重写此类型的hashCode()方法并使其正常工作,对吗?最后一个问题是:hashcode()对于列表也很重要,例如ArrayList吗?那么没有“Hash”在名称中的集合呢,例如TreeSet?我猜TreeSet是使用树而不是哈希表实现的,因此hashCode()是否相关? - felipeek
哈希码并不被 ArrayListLinkedListTreeSet 使用。它被基于哈希的集合 HashMapHashSetConcurrentHashMap 等使用。您可以随时检查集合类的 javadoc,它应该记录是否基于哈希。 - David Conrad
@David Conrad 非常感谢!你们解决了我所有的疑问 :) - felipeek

4

如果你查看HashSet的源代码(它随JDK一起提供而且非常信息化),你会发现它创建了一个对象作为值:

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

每个添加到HashSet中的值都被用作背后HashMap的键,而这个值是PRESENT对象。
当你重写hashCode()(或者反过来)时,关于重写equals(),非常重要的一点是这两个方法应该返回一致的结果。也就是说,它们应该相互协调。更多细节请参见Josh Bloch的书《Effective Java》

非常感谢。这帮了很多忙。 - felipeek
如果这个答案对你有帮助,@user3513453 你可以点赞。 - David Conrad
实际上我不能,我需要15个声望点:P - felipeek

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