HashSet包含自定义对象的问题

时间:2011-02-24 20:56:41

标签: java hashcode hashset

我的自定义类,将由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

4 个答案:

答案 0 :(得分:29)

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

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

答案 1 :(得分:5)

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.

在您的示例中就是这种情况,因为更改name上的agep1会影响相等比较。所以根据ApiDoc,你的案例中Set的行为是未指定的。

答案 2 :(得分:1)

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

<强>之前:

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 :(得分:1)

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

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

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

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