我正在为这些接口实现一个值对象:
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 interface和toString(), 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;
}
}
我的问题是关于我在这里添加的最后一点,以及如何处理使用AbstractFooKey.equals(x)
的值调用x
的情况,FooKey
是实现{{1}的类的实例但是没有子类AbstractFooKey
。我不知道该如何处理;一方面,我觉得相等的语义应该只取决于名称和serialNumber是否相等,但似乎hashCodes也必须相等,以满足Object.equals()的契约。
我应该是:
***
false
对象不是equals()
,则other
返回AbstractFooKey
答案 0 :(得分:2)
记录所需的语义作为合同的一部分。
理想情况下,您实际上只有一个实现是最终的,这种做法否定了为特定目的接口的需要。您可能有其他原因需要该类型的接口。
Object
的合同要求实际上来自hashCode
:If 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.
您无需在hashCode
计算中加入equals
,而是需要在equals
计算中包含hashCode
中涉及的所有属性。在这种情况下,我只需比较serialNumber
和name
中的equals
和hashCode
。
保持简单,除非你有真正的理由使其复杂化。
从最终的不可变类开始。
如果您需要一个接口,请创建一个以匹配,并记录语义和默认实现。
答案 1 :(得分:1)
对于equals和hashmap,有严格的合同:
Reflexive - 它只是意味着对象必须等于它自己,它在任何给定的实例中都是如此;除非你故意重写equals方法否则表现。
对称 - 这意味着如果一个类的对象等于另一个类对象,则另一个类对象必须等于此类对象。换句话说,一个对象不能单方面决定它是否与另一个对象相等;两个对象,以及它们所属的类,必须双边决定它们是否相等。他们必须同意。
传递性 - 这意味着如果第一个对象等于第二个对象而第二个对象等于第三个对象;那么第一个对象就等于第三个对象。换句话说,如果两个对象同意它们是相等的,并遵循对称原则,则其中一个不能决定与另一个不同类别的对象有类似的契约。对于这三个类别的各种排列,所有三个必须同意并遵循对称原则。
一致 - 这意味着如果两个对象相等,只要它们未被修改,它们就必须保持相等。同样,如果它们不相等,只要它们没有被修改,它们就必须保持不相等。修改可以在它们中的任何一个中进行,也可以在它们两者中进行。
null比较 - 这意味着任何可实例化的类对象都不等于null,因此如果将null作为参数传递给它,则equals方法必须返回false。如果将null作为参数传递给它,则必须确保equals方法的实现返回false。
hashCode()的合同:
同一执行期间的一致性 - 首先,它指出hashCode方法返回的哈希码在同一个应用程序执行期间对于多次调用必须始终相同,只要不修改对象以影响equals方法。
哈希代码&等于关系 - 合同的第二个要求是equals方法指定的要求的hashCode对应物。它只是强调相同的关系 - 相等的对象必须产生相同的哈希码。然而,第三点阐述了不相等的对象不需要产生不同的哈希码。
(来自:Technofundo: Equals and Hash Code)
但是,在equals中使用 instanceof 不是正确的做法。 Joshua Bloch在Effective Java中对此进行了详细介绍,您对equals实现的有效性的关注是有效的。最有可能的是,使用instanceof时出现的问题在与基类的后代一起使用时会违反合同中的及物性部分 - 除非等效函数是最终的。
(比我在这里做的好一点:Stackoverflow: Any reason to prefer getClass() over instanceof when generating .equals()?)
同时阅读:
答案 2 :(得分:0)
如果FooKey的相等性使得具有相同名称和序列号的两个FooKeys被认为是相等的,那么您可以删除比较哈希码的equals()子句中的行。
或者你可以保留它,假设FooKey接口的所有实现者都有正确的equals和gethashcode实现并不重要,但我建议删除它,因为否则代码的读者会得到它的印象是因为它有所作为,而实际上并没有。
你也可以去掉gethashcode方法中的'* 37',它不太可能有助于更好的哈希码分发。
就你的问题3而言,我会说不,不要这样做,除非FooKey的平等合约不受你控制(在这种情况下,试图强制执行接口的平等合同无论如何都是有问题的)< / p>