这个问题在这里已有答案:
考虑员工类 -
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);
将它们添加到hashset后,大小将为2. HashSet内部使用hashmap来保持唯一性 -
private transient HashMap<E,Object> map;
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
现在,如果我将第二个员工的id设置为与第一个员工的id相同,即 -
employer2.setId(10L);
大小仍然是2.为什么不是1? in-variants是否会被破坏?
所有基于散列的容器(包括HashSet<T>
)都对其键的哈希码做出了非常重要的假设:它们假设哈希代码在对象位于容器内时永远不会改变。
您的代码通过在实例仍在哈希集中时修改实例来违反此假设。 HashSet<T>
没有实际的方法来应对这种变化,因此您必须选择以下两种方法之一来解决此问题:
第二种方法经常成为维护头痛的根源。当您需要在基于散列的容器中保留可变数据时,一种好方法是在计算哈希代码和相等性检查时仅使用final
字段。在你的例子中,这意味着制作id
字段final
,并从类中删除setId
方法。
大小仍然是2.为什么不是1? in-variants是否会被破坏?
如果您修改用于计算hashCode
中已有的实例的equals
和HashSet
的任何属性,则HashSet
实现不知道该更改。
因此它将保留这两个实例,即使它们现在彼此相等。
您不应对作为成员或HashSet
s(或HashMap
s中的键)的实例进行此类更新。如果必须进行此类更改,请在变更之前从Set
中删除该实例,然后再重新添加。