繁体   English   中英

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

[英]Should hashCode() only use the subset of immutable fields of those used in equals()?

情况

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

set.contains(object)
=> false

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

我现在明白了,这是因为在将object添加到set之后对object所做的更改再次更改了其hashCode。 然后contains查找错误的键并找不到该object

我对实施的要求是

  • 需要可变字段才能正确实现equals()
  • 即使它们容易发生变化,也可以在基于散列的CollectionsMaps安全地使用这些对象,例如灰烬HashSet

这违反了惯例

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

更具体而言,这将意味着: equals()使用了编号的对象,而字段的hashCode()只使用了在用于这些领域equals()和是不可变的

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

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

相关帖子帮助我理解了我的问题,但没有解决方法: 在Java中覆盖equals和hashCode时应该考虑哪些问题? equals和hashcode的不同字段

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

顺便说一句,IntelliJ在生成hashCode时提供了一个很好的向导,并且通过分别处理这两个方法并允许区分您的选择来提供相同的向导。 显然,相反,又名提供在更多领域hashCode()和更少的领域中equals()违反约定。

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

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

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

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

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

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

name决定条目的位置(想想HashMap )或选择哪个桶。

你把一个Person作为Key放在HashMap :根据hashcode它会进入某个桶,例如第二个。 您升级income并在地图中搜索该Person 根据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使用更多的字段,因此它是错误的。

如果您了解这些细节,那么第一个哈希码用于了解Where和Entry 可能驻留的位置,并且只有稍后所有这些(来自子集或存储桶)都试图通过equal方式找到 - 您的示例才有意义。

hashCode()可以使用equals()使用的字段的子集,尽管它可能会给你一点性能下降。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM