hashCode()只应使用equals()中使用的不可变字段的子集吗?

时间:2017-09-18 11:49:54

标签: java override equals hashcode contract

场合

我需要覆盖equals(),因为建议我也使用相同的字段覆盖hashCode()方法。然后,当我看到一个只包含一个对象的集合时,我得到了令人沮丧的结果

set.contains(object)
=> false

,而

set.stream().findFirst().get().equals(object)
=> true

我现在明白了,这是因为object添加到set后再次更改其hashCode所做的更改。 contains然后查看错误的密钥,找不到object

我对实施的要求是

  • 需要可变字段才能正确实现equals()
  • 在基于哈希的CollectionsMaps这样的灰HashSet中安全地使用这些对象,即使它们容易发生变化。

的惯例冲突

问题

仅使用equals()中用于计算hashCode()而非使用全部的字段的子集是否有任何危险?

更具体地说,这意味着:equals()使用对象的多个字段,而hashCode()仅使用 equals() 中使用的字段是不可变的

我认为这应该没问题,因为

  • contract已满:相同的对象将产生相同的hashCode,而相同的hashCode并不意味着对象是相同的。
  • 对象的hashCode保持不变,即使对象暴露于更改,因此会在这些更改之前和之后的HashSet中找到。

相关帖子帮助我了解了我的问题,但没有解决方法:What issues should be considered when overriding equals and hashCode in Java?Different fields for equals and hashcode

4 个答案:

答案 0 :(得分:0)

合同确实会实现。合同规定.equal()个对象始终相同.hashCode()。相反的情况并非必须如此,我不知道有些人和IDE的痴迷恰恰适用于这种做法。如果所有可能的组合都可以,那么你会发现完美的哈希函数。

BTW,IntelliJ在生成hashCode时提供了一个很好的向导,并且通过分别处理这两个方法并允许区分您的选择来提供等效。显然,相反的是,hashCode()中的equals()以及更少的字段中提供更多字段会违反合同。

答案 1 :(得分:0)

对于HashSet和类似的集合/地图,让hashCode()仅使用equals()方法中的字段子集是一种有效的解决方案。当然,您必须考虑哈希码有效减少地图中的冲突。

但请注意,如果您想使用TreeSet之类的有序集合,则会出现问题。然后你需要一个永远不会给“不同”对象发生冲突(返回零)的比较器,这意味着该集合只能包含一个碰撞元素。您的equals()描述意味着将存在多个仅在可变字段中不同的对象,然后您将丢失:

  • 在compareTo()方法中包含可变字段可以更改比较符号,以便对象需要移动到树中的其他分支。
  • 排除compareTo()方法中的可变字段会限制您在TreeSet中拥有最多一个碰撞元素。

所以我强烈建议再考虑你的对象类的平等和可变性概念。

答案 2 :(得分:0)

这对我来说完全有效。假设您有一个Person

 final int name; // used in hashcode
 int income; // name + income used in equals

name决定参赛作品的位置(考虑HashMap)或选择哪个参赛作品。

您将Person作为Key置于HashMap内:根据hashcode,它会转到某个存储桶,例如第二个。您升级income并在地图中搜索Person。根据{{​​1}},它必须在第二个桶中,但根据hashcode,它不在那里:

equals

测试:

 static class Person {
    private final String name;

    private int income;

    public Person(String name) {
        super();
        this.name = name;
    }

    public int getIncome() {
        return income;
    }

    public void setIncome(int income) {
        this.income = income;
    }

    public String getName() {
        return name;
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object other) {
        Person right = (Person) other;

        return getIncome() == right.getIncome() && getName().equals(right.getName());
    }

}

你错过了我认为 HashSet<Person> set = new HashSet<>(); Person bob = new Person("bob"); bob.setIncome(100); set.add(bob); Person sameBob = new Person("bob"); sameBob.setIncome(200); System.out.println(set.contains(sameBob)); // false 决定一个条目所在的桶(该桶中可能有许多条目)这是第一步,但是hashcode决定是否很好,一个平等的进入。

您提供的示例完全合法;但是您链接的是另一种方式 - 它使用equals中的更多字段,因此不正确。

如果您了解这些详细信息,则第一个哈希码用于了解可能驻留的位置,并且仅在以后所有这些(来自子集或存储桶)尝试通过hashcode找到 - 你的例子是有意义的。

答案 3 :(得分:0)

hashCode()可以使用equals()使用的字段子集,但它可能会让您略微降低性能。

您的问题似乎是由于修改了对象,同时仍然在集合内部,以改变hashCode()和/或equals()的功能。每当您将对象添加到HashSet(或作为HashMap中的键)时,必须随后修改equals()和/或{{1}使用的该对象的任何字段}}。理想情况下,hashCode()使用的所有字段都应为equals()。如果它们不可能,则必须将它们视为最终,而对象在集合中。

TreeSet / TreeMap也是如此,但适用于final使用的字段。

如果您确实需要修改compareTo()(或者在TreeSet / TreeMap的情况下由equals())使用的字段,您必须:

  1. 首先,从集合中删除该对象;
  2. 然后修改对象;
  3. 最后将它添加回集合。