[英]How to properly solve “Design For Extension” CheckStyle warning on hashCode and equals
鉴于:
public class Foo {
@Override
public boolean equals(final Object obj) {
// implementation
}
@Override
public int hashCode() {
// implementation
}
}
和
public class Bar extends Foo {
@Override
public boolean equals(final Object obj) {
// different implementation
}
@Override
public int hashCode() {
// different implementation
}
}
我理解为什么Checkstyle给我“设计扩展:方法'hashCode'不是为扩展而设计的 - 需要是抽象的,最终的或空的。” 该方法既不是最终的,也不是抽象的或空的。 但我怎么能实现这一目标,而不是违反任何OO规则或指南? 有关如何使用此示例的示例:
Foo(为了简洁起见,我使用了默认的Object实现)
public class Foo {
public int getX() {
return x;
}
public void setX(final int x) {
this.x = x;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
private int x;
}
public class Bar extends Foo {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = result * prime + getX();
return result;
}
@Override
public boolean equals(final Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Bar)) {
return false;
}
final Bar bar = (Bar) obj;
if (bar.getX() != this.getX()) {
return false;
}
return true;
}
}
public static void main(final String[] args) {
final Bar barOne = new Bar();
barOne.setX(1);
final Bar barTwo = new Bar();
barTwo.setX(1);
final Map<Bar, String> barMap = new HashMap<>();
barMap.put(barOne, null);
barMap.put(barTwo, null);
System.out.println(barMap.size());
final Foo fooOne = new Foo();
fooOne.setX(1);
final Foo fooTwo = new Foo();
fooTwo.setX(1);
final Map<Foo, String> fooMap = new HashMap<>();
fooMap.put(fooOne, null);
fooMap.put(fooTwo, null);
System.out.println(fooMap.size());
}
输出
1
2
在这种情况下,在地图中使用Bar会显示Map中只有一个元素,因为在将值放入Map时,它会检查是否已存在与现有hashCode的元素。 在默认的Object实现(Foo)中,它为(相对)创建的所有对象创建唯一值。 Bar类实现基于x字段。 当它被放入地图时,只存在一个元素,因为hashCode是相同的。
那么,有没有办法满足要求,同时仍然可以在前面的例子中定义Foo和Bar?
我担心你遇到了一个令人讨厌的OO陷阱,不仅限于Java。
在我希望父和子可以互换的情况下,我发现简化的解决方案是在我的基类上实现它们之后永远不会覆盖hashcode和equals(显然从java.lang.Object重写是好的)。 如果一个人不关心它们是否可以互换,那么就没有问题了。
不遵循hashcode / equals契约几乎总是遇到一个OO陷阱,当在hashmaps和sets中使用对象时,可能会遇到奇怪的错误。 请记住,为闭合扩展设计的类应该适用于它扩展的类,也称为liskov替换规则。
有关示例的完整详细信息由Joshua Bloch在Effective Java的第8项中记录。 约书亚的主要引用是(见第38页底部):
除非您愿意忘记OO抽象的好处,否则无法扩展可实例化的类并在保留equals合同的同时添加值组件。
如果这听起来太过分了,请记住,Joshua Bloch曾在Sun帮助创建Java。 他非常了解这个领域,他的书经得起时间的考验,成为那里最“必读”的Java开发书之一。
让我们具体说明这个问题吧。
我们有对象a
, b
和c
。
我们有a.equals(b)
和a.equals(c)
。 通过合同,我们还必须有b.equals(c)
。
a
是Base
一个实例; b
和c
是Derived
实例。
现在尝试定义这样一个Derived#equals
,以满足合同。 你别无选择,只能将它与已经为Base
定义的完全相同。
注意,逆向关系也必须成立:如果a
等于b
而不是c
,那么b
不能等于c
。 从更广义的角度来看, equals
必须将对象划分为完全相同的相等类 ,无论它们是否被视为Base
或Derived
实例。
您的问题是关于hashCode()
,但由于众所周知的要求hashCode
必须与equals
一致,因此适用相同的catch。
我确信这不会回答'如何解决'检查方式警告。 仍然加我的两分钱。 IMO,扩展设计,即使被称为开放封闭原则,这里也意味着hashcode / equals方法遵循Liskov Substitution Principle 。 大多数情况下,它是通过抽象来实现的,正如克里斯指出的那样,参考Joshua Bloch的评论,你不能在不影响平等合同的情况下为可实例化的(非抽象)类增加一个重要的字段。 我认为以完美的抽象层次为目标可能并不总是值得的。 您可能必须使用不符合OCP和LSP的解决方案。 这是一个判断电话。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.