【问题标题】:Should hashCode() only use the subset of immutable fields of those used in equals()?hashCode() 是否应该只使用 equals() 中使用的不可变字段的子集?
【发布时间】:2017-09-18 11:49:54
【问题描述】:

情况

我需要覆盖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

【问题讨论】:

    标签: java overriding equals hashcode contract


    【解决方案1】:

    hashCode() 可以使用 equals() 使用的字段的子集,尽管这可能会使您的性能略有下降。

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

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

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

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

    【讨论】:

      【解决方案2】:

      合同确实会履行。合同规定.equal() 对象具有始终相同的.hashCode()。相反不一定是真的,我想知道某些人和 IDE 是否痴迷于应用这种做法。如果这对所有可能的组合都是可能的,那么您会发现完美的哈希函数。

      顺便说一句,IntelliJ 在生成 hashCode 和 equals 时提供了一个很好的向导,通过分别处理这两种方法并允许区分您的选择。显然,相反,在hashCode() 中提供更多字段而在equals() 中提供更少字段将违反合同。

      【讨论】:

        【解决方案3】:

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

        但请注意,如果您想使用像 TreeSet 这样的有序集合,问题就会再次出现。然后,您需要一个永远不会为“不同”对象提供碰撞(返回零)的比较器,这意味着该集合只能包含一个碰撞元素。您的equals() 描述意味着将存在多个仅在可变字段中不同的对象,然后您就输了:

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

        因此,我强烈建议您再次考虑一下您的对象类的平等和可变性概念。

        【讨论】:

          【解决方案4】:

          这对我来说完全有效。假设你有一个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 中使用 more 字段,因此不正确。

          如果您了解这些详细信息,则 first 哈希码用于了解条目 可能驻留的位置和位置,然后才尝试所有这些(来自子集或存储桶)可以通过equal 找到 - 你的例子很有意义。

          【讨论】:

            猜你喜欢
            • 2011-07-11
            • 2011-11-12
            • 2011-06-13
            • 1970-01-01
            • 2011-11-14
            • 2014-05-14
            • 1970-01-01
            • 1970-01-01
            • 2011-02-26
            相关资源
            最近更新 更多