繁体   English   中英

如何在hashCode和equals上正确解决“Design For Extension”CheckStyle警告

[英]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 BlochEffective Java的第8项中记录。 约书亚的主要引用是(见第38页底部):

除非您愿意忘记OO抽象的好处,否则无法扩展可实例化的类并在保留equals合同的同时添加值组件。

如果这听起来太过分了,请记住,Joshua Bloch曾在Sun帮助创建Java。 他非常了解这个领域,他的书经得起时间的考验,成为那里最“必读”的Java开发书之一。

让我们具体说明这个问题吧。

  1. 我们有对象abc

  2. 我们有a.equals(b)a.equals(c) 通过合同,我们还必须b.equals(c)

  3. aBase一个实例; bcDerived实例。

现在尝试定义这样一个Derived#equals ,以满足合同。 你别无选择,只能将它与已经为Base定义的完全相同。

注意,逆向关系也必须成立:如果a等于b而不是c ,那么b不能等于c 从更广义的角度来看, equals必须将对象划分为完全相同的相等类 ,无论它们是否被视为BaseDerived实例。

您的问题是关于hashCode() ,但由于众所周知的要求hashCode必须与equals一致,因此适用相同的catch。

我确信这不会回答'如何解决'检查方式警告。 仍然加我的两分钱。 IMO,扩展设计,即使被称为开放封闭原则,这里也意味着hashcode / equals方法遵循Liskov Substitution Principle 大多数情况下,它是通过抽象来实现的,正如克里斯指出的那样,参考Joshua Bloch的评论,你不能在不影响平等合同的情况下为可实例化的(非抽象)类增加一个重要的字段。 我认为以完美的抽象层次为目标可能并不总是值得的。 您可能必须使用不符合OCP和LSP的解决方案。 这是一个判断电话。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM