【问题标题】:HashSet contains problem with custom objectsHashSet 包含自定义对象的问题
【发布时间】:2011-07-03 21:08:45
【问题描述】:

HashSet 将包含的我的自定义类

public class Person {
    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "hashcode='" + this.hashCode() + '\'' +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        if (!name.equals(person.name)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

我的 HashSet 测试失败

   public void hashSetTest() {
        Set<Person>  personSet = new HashSet<Person>();
        Person p1 = new Person("raghu", 12);
        Person p2 = new Person("rimmu", 21);

        personSet.add(p1);
        personSet.add(p2);


       p1.setName("raghus");
       p1.setAge(13);

       int i2 =p1.hashCode();
       System.out.println(personSet.size() + ": "+ p1.hashCode()+" : "+personSet.contains(p1)+ " : "+i2);
    }

我期待 personSet.contains(p1) 通过。为什么它返回错误? 谢谢 sri

【问题讨论】:

    标签: java hashcode hashset


    【解决方案1】:

    因为修改p1p1.hashCode()会发生变化,所以在哈希表的原始索引处找不到它了。永远不要让哈希值依赖于可变字段。

    (你很幸运,它在测试期间失败了;它也可能成功了,只是在生产中失败了。)

    【讨论】:

    • 好吧,“从不”有点苛刻,因为在某些情况下您会需要它。请记住,您必须删除这样的对象,对其进行修改并重新添加到集合中。但显然,如果可能的话,最好只使用不可变字段。
    • @Voo:这可能是风格和偏好的问题,但我还是会说从不。散列函数应该基于对象的不可变部分来定义。 (我会制作一个完全不可变的轻量级对象,但是我根本不喜欢 setter。)
    • 这肯定是一个你必须注意的陷阱,但是如果你需要有效地访问(~O(1))可变对象,你无能为力。我的意思是您可以使对象不可变并创建一个新对象并添加该对象并删除旧对象,但是这是否比另一种方法更好?
    • 你应该将他的答案标记为正确(除了赞成/反对票之外的绿旗)。
    • 感谢您的快速回复。你们是说在 HashTable 中具有 HashCode xxxx 位置的原始 p1 对象说 '1' 在经过修改后仍然在 HashTable 中的相同位置 '1' 被索引,并且具有不同的 HashCode yyyy?这就是为什么 contains 方法实际上没有在其集合中找到对象 p1 因为现在 HashCode yyyy 显示不同的索引? --sri
    【解决方案2】:

    哈希是键和值的简单配对。以下是在伪代码中重命名之前和之后的代码状态:

    之前:

    personSet => {
        SOME_NUM1 => Person(name=>"raghu", 12),
        SOME_NUM2 => Person(name=>"rimmu", 21)
    }
    
    p1.setName("raghus"); #p1.hashcode() = SOME_NEW_NUM
    p1.setAge(13);#p1.hashcode() = SOME_OTHER_NEW_NUM
    

    之后:

    personSet => {
        SOME_NUM1 => Person(name=>"raghu", 13),
        SOME_NUM2 => Person(name=>"rimmu", 21)
    }
    

    由于您可以直接访问 p1,因此 HashSet 中的对象已正确更新,但 HashSet 不注意包含的对象哈希码正在更新。当调用personSet.contains(p1) 时,HashSet 会查找新值为p1.hashcode() 的条目。

    p1 对象在添加到 HashSet 时与其先前的哈希码相关联。

    【讨论】:

      【解决方案3】:

      HashSet 实现Set。 ApiDoc 指定:

      Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.

      在您的示例中就是这种情况,因为在 p1 上更改 nameage 会影响相等比较。因此,根据 ApiDoc,您的情况下 Set 的行为是未指定的。

      【讨论】:

        【解决方案4】:

        我认为你确实需要经常让 hashCode 依赖于可变字段:当你覆盖依赖于可变字段的 equals 时。

        来自 hashCode 的合约:“如果两个对象根据 equals(Object) 方法相等,那么对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。”

        因此,如果您创建两个对象以使 A.equals(B) 为真,然后修改 A 以使 A.equals(B) 变为假,则您也需要更改 hashCodes。

        确实,在 hashCode 的文档中声明“如果根据 equals(java.lang.Object) 方法,如果两个对象不相等,那么对两个对象中的每一个调用 hashCode 方法都必须产生不同的整数结果。”,但我不知道这有什么帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-01-30
          • 1970-01-01
          • 2015-09-07
          • 1970-01-01
          • 2022-11-23
          • 2019-03-31
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多