【问题标题】:Why doesn't HashSet maintain uniqueness? [duplicate]为什么 HashSet 不保持唯一性? [复制]
【发布时间】:2018-06-11 12:36:11
【问题描述】:

考虑员工类 -

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 内部使用 hashmap 来保持唯一性-

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

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

employer2.setId(10L);

大小仍然是 2。 为什么不是1?内部变量会被破坏吗?

【问题讨论】:

标签: java hashset


【解决方案1】:

所有基于散列的容器,包括HashSet&lt;T&gt;,都对其键的散列码做了一个非常重要的假设:它们假设当对象在容器内时散列码永远不会改变。

您的代码违反了这一假设,即在实例仍在哈希集中时对其进行了修改。 HashSet&lt;T&gt; 没有切实可行的方法来应对此更改,因此您必须选择以下两种方法之一来处理此问题:

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

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

【讨论】:

  • 没错。我在阅读 Item39:Effective Java 中的相同内容后才知道 - 例如,如果您正在考虑使用客户端提供的对象引用作为内部 Set 实例中的元素或作为内部 Map 实例中的键,您应该知道如果对象在插入后被修改,则集合或映射的不变量将被破坏。
  • 最终我会弄清楚是什么驱使你一遍又一遍地回答同一个问题。在那之前,2018 年快乐!
  • @SotiriosDelimanolis 为什么要弄清楚什么时候可以问?原因很简单——我一打完就忘记了大部分答案。如果我认为我之前可能已经回答过这个问题,我会快速搜索一下是否能找到相关的内容。否则,我输入一个答案。新年快乐!
【解决方案2】:

大小仍然是 2。为什么不是 1?内部变量会被破坏吗?

如果您修改用于计算 HashSet 中已存在实例的 hashCodeequals 的任何属性,HashSet 实现不会意识到该更改。

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

您不应为成员或HashSets(或HashMaps 中的键)的实例进行此类更新。如果您必须进行此类更改,请先从 Set 中删除该实例,然后再对其进行变异并稍后重新添加。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-08
    • 1970-01-01
    • 2018-09-14
    • 1970-01-01
    • 2019-08-30
    • 1970-01-01
    相关资源
    最近更新 更多