为什么哈希集不保持唯一性?



考虑员工类 -

public class Employer implements Serializable{
private Long id;
private String name;
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj instanceof Employer) {
Employer employer = (Employer) obj;
if (this.id == employer.id) {
return true;
} 
}
return false;
}
//Idea from effective Java : Item 9
@Override
public int hashCode() {
int result = 17;
result = 31 * result + id.hashCode();
//result = 31 * result + name.hashCode();
return result;
}
}

创建了 2 个员工对象 -

Employer employer1 = new Employer();
employer1.setId(10L);
Employer employer2 = new Employer();
employer2.setId(11L);

将它们添加到哈希集后,大小将为 2。 HashSet 内部使用哈希图来保持唯一性-

private transient HashMap<E,Object> map;
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

现在,如果我将第二个员工的 id 设置为与第一个员工的 id 相同,即

employer2.setId(10L);

大小仍为 2。 为什么不是1?变体会被摧毁吗?

所有基于哈希的容器,包括HashSet<T>,都对其键的哈希代码做出了一个非常重要的假设:它们假设当对象在容器内时,哈希代码永远不会改变。

您的代码在实例仍在哈希集中时对其进行修改,从而违反了此假设。HashSet<T>没有实际的方法对此更改做出反应,因此您必须选择以下两种方法之一来处理此问题:

  • 切勿修改基于哈希的容器的密钥- 这是迄今为止最常见的方法,通常通过使哈希密钥不可变来实现。
  • 跟踪修改,并手动重新哈希对象- 本质上,您的代码确保对哈希键的所有修改都在容器外部发生:从容器中删除对象,进行修改,然后将其放回原处。

第二种方法经常成为维护头痛的根源。当您需要将可变数据保存在基于哈希的容器中时,一个好的方法是在计算哈希代码和相等性检查时仅使用final字段。在您的示例中,这意味着id字段final,并从类中删除setId方法。

大小

仍保持为 2。为什么不是1?变体会被摧毁吗?

如果修改用于为HashSet中已有的实例计算hashCodeequals的任何属性,则HashSet实现不知道该更改。

因此,它将保留这两个实例,即使它们现在彼此相等。

您不应为成员或HashSets(或HashMaps 中的键)的实例进行此类更新。如果必须进行此类更改,请在更改实例之前将其从Set中删除,稍后再重新添加。

最新更新