2

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

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();
}

从我读过的内容来看(例如在接口中强制执行“equals”和接口中的toString ()、equals() 和 hashCode())看起来建议是提供一个抽象基类,例如

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;
    }
}

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

我可以做:

  1. 真的很松懈,只是忘记了标记的线***
  2. 松懈并保留我所拥有的
  3. 如果对象不是false_equals()otherAbstractFooKey
  4. 真的很严格并摆脱接口 FooKey 并将其替换为最终类吗?
  5. 别的东西?
4

3 回答 3

2

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

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

的合同要求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.

您不需要包含hashCodeequals计算中,而是需要包含计算中涉及的所有equals属性hashCode。在这种情况下,我会简单地比较serialNumberandname两者中的equalsand hashCode

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

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

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

于 2012-10-09T15:13:08.797 回答
1

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

  1. Reflexive - 它只是意味着对象必须等于它自己,它在任何给定的实例中都是如此;除非您故意重写 equals 方法以使其行为不同。

  2. 对称- 这意味着如果一个类的对象等于另一个类对象,则另一个类对象必须等于该类对象。换句话说,一个对象不能单方面决定它是否等于另一个对象;两个对象,以及它们所属的类,必须双边决定它们是否相等。他们都必须同意。

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

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

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

hashCode() 的合约:

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

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

(来自:Technofundo:等于和哈希码

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

(比我在这里做的要详细一点:Stackoverflow:任何理由在生成 .equals() 时更喜欢 getClass() 而不是 instanceof?

另请阅读:

于 2012-10-09T15:06:52.367 回答
0

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

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

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

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

于 2012-10-09T15:08:06.603 回答