【问题标题】:subtleties of dealing with equals and hashCode in a Java interface在 Java 接口中处理 equals 和 hashCode 的微妙之处
【发布时间】:2012-09-29 22:50:55
【问题描述】:

我正在为这些接口实现一个值对象:

interface FooConsumer
{
    public void setFoo(FooKey key, Foo foo);
    public Foo getFoo(FooKey key);
}

// intent is for this to be a value object with equivalence based on 
// name and serial number
interface FooKey
{
    public String getName();
    public int getSerialNumber();
}

从我读到的内容(例如在Enforce "equals" in an interfacetoString(), equals(), and hashCode() in an interface)看来,建议是提供一个抽象基类,例如

abstract class AbstractFooKey
{
    final private String name;
    final private int serialNumber
    public AbstractFooKey(String name, int serialNumber)
    {
       if (name == null)
          throw new NullPointerException("name must not be null");
       this.name = name;
       this.serialNumber = serialNumber;
    }
    @Override public boolean equals(Object other)
    {
       if (other == this)
          return true;
       if (!(other instanceof FooKey))
          return false;
       return getName().equals(other.getName() 
          && getSerialNumber() == other.getSerialNumber()
          && hashCode() == other.hashCode();       // ***
    }
    @Override public int hashCode()
    {
       return getName().hashCode() + getSerialNumber()*37;
    }
}

我的问题是关于我在此处添加的最后一点,以及如何处理使用 x 的值调用 AbstractFooKey.equals(x) 的情况,这是实现 FooKey 但没有子类的类的实例AbstractFooKey。我不知道如何处理这个;一方面,我觉得相等的语义应该只取决于 name 和 serialNumber 是否相等,但看起来 hashCodes 也必须相等才能满足 Object.equals() 的合同。

我应该是:

  1. 真的很松懈,忘记标记***的行
  2. 放松并保留我所拥有的
  3. 如果other对象不是AbstractFooKey,则从equals()返回false
  4. 真的很严格,去掉接口 FooKey 并用一个最终的类替换它?
  5. 还有别的吗?

【问题讨论】:

  • 行标***是不正常的。 equals() 实现通常不调用 hashCode()。

标签: java equals hashcode


【解决方案1】:

将所需的语义记录为合同的一部分。

理想情况下,您实际上应该有一个最终的实现,这样就不需要接口为此特定目的。您可能有其他原因需要该类型的接口。

Object的合约需求其实来自hashCodeIf two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

您不需要在equals 计算中包含hashCode,而是需要在hashCode 计算中包含equals 中涉及的所有属性。在这种情况下,我只需比较equalshashCode 中的serialNumbername

保持简单,除非你有真正的理由让它复杂化。

从一个最终的、不可变的类开始。

如果您需要一个接口,请创建一个与之匹配的接口,并记录语义和默认实现。

【讨论】:

  • 您的断言“您需要在 hashCode 计算中包含 equals 中涉及的所有属性”是不正确的。 hashCode 只需要拥有equals 属性的子集。极端地说,public int hashCode() { return 123; } 是完全有效的。
【解决方案2】:

对于equals和hashmap,有严格的约定:

    1234563除非您故意重写 equals 方法以使其行为不同。
  1. 对称 - 这意味着如果一个类的对象等于另一个类对象,那么另一个类对象必须等于这个类对象。换句话说,一个对象不能单方面决定它是否等于另一个对象;两个对象,以及它们所属的类,必须双边决定它们是否相等。他们都必须同意。

  2. Transitive - 表示如果第一个对象等于第二个对象,第二个对象等于第三个对象;那么第一个对象等于第三个对象。换句话说,如果两个对象同意它们是平等的,并且遵循对称原则,那么它们中的一个不能决定与另一个不同类的对象具有相似的契约。对于这三类的各种排列,这三者必须一致并遵循对称原则。

  3. 一致 - 这意味着如果两个对象相等,则只要它们不被修改,它们就必须保持相等。同样,如果它们不相等,只要它们未被修改,它们就必须保持不相等。修改可以发生在其中任何一个或两者中。

  4. null 比较 - 这意味着任何可实例化的类对象都不等于 null,因此如果将 null 作为参数传递给 equals 方法,则必须返回 false。如果将 null 作为参数传递给它,您必须确保您的 equals 方法的实现返回 false。

hashCode() 的合约:

  1. 同一执行期间的一致性 - 首先,它声明 hashCode 方法返回的哈希码必须在应用程序的同一执行期间的多次调用中保持一致,只要对象未修改以影响 equals 方法。

  2. Hash Code & Equals 关系 - 合约的第二个要求是由 equals 方法指定的要求的 hashCode 对应项。它只是强调相同的关系——相等的对象必须产生相同的哈希码。然而,第三点阐述了不相等的对象不需要产生不同的哈希码。

(来自:Technofundo: Equals and Hash Code

但是,在 equals 中使用 instanceof 并不是正确的做法。 Joshua Bloch 在 Effective Java 中详细介绍了这一点,您对 equals 实现有效性的担忧是有效的。最有可能的是,当与基类的后代一起使用时,使用 instanceof 产生的问题将违反合同中的传递性部分 - 除非将 equals 函数设为 final。

(比我在这里做的详细一点:Stackoverflow: Any reason to prefer getClass() over instanceof when generating .equals()?

另请阅读:

【讨论】:

  • 有点晚了,但你是什么意思,Bloch 在 Effective Java 中详细说明在 equals 方法中使用 instanceof 不是正确的做法?引用 Effective Java 第 2 版(第 42 页)的标题:“这里有一个高质量 equals 方法的秘诀:”,您会发现:“2. 使用 instanceof 运算符检查参数是否具有正确的类型。”他还建议使用 instanceof 来查看传递的对象是否为 null,而不是添加额外的 if ( o == null ) return false;
【解决方案3】:

如果 FooKey 的相等性使得具有相同名称和序列号的两个 FooKey 被认为是相等的,那么您可以删除 equals() 子句中比较哈希码的行。

或者你可以把它留在里面,假设 FooKey 接口的所有实现者都正确实现了 equals 和 gethashcode 并不重要,但我建议删除它,否则代码的读者可能会觉得它是否存在,因为它会产生影响,而实际上它不会。

您也可以去掉 gethashcode 方法中的 '*37',它不太可能有助于更好的哈希码分布。

就您的问题 3 而言,我会说不,不要那样做,除非 FooKey 的平等合同不受您控制(在这种情况下,尝试强制执行接口的平等合同无论如何都是有问题的)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-09
    相关资源
    最近更新 更多