可重用的equals和hashCode实现

时间:2017-01-25 09:18:52

标签: java equals hashcode

我正在开展一个项目,其中许多类需要equalshashCode的适当典型实现:每个类都有一组在构造时初始化的最终字段" deep&# 34;不可变对象(在某些情况下可以接受null)用于散列和比较。

为了减少样板代码的数量,我考虑编写一个抽象类来提供这种行为的常见实现。

public abstract class AbstractHashable {

    /** List of fields used for comparison. */
    private final Object[] fields;

    /** Precomputed hash. */
    private final int hash;

    /**
     * Constructor to be invoked by subclasses.
     * @param fields list of fields used for comparison between objects of this
     * class, they must be in constant number for each class
     */
    protected AbstractHashable(Object... fields) {
        this.fields = fields;
        hash = 31 * getClass().hashCode() + Objects.hash(fields);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj == null || !getClass().equals(obj.getClass())) {
            return false;
        }
        AbstractHashable other = (AbstractHashable) obj;
        if (fields.length != other.fields.length) {
            throw new UnsupportedOperationException(
                    "objects of same class must have the same number of fields");
        }
        for (int i=0; i<fields.length; i++) {
            if (!fields[i].equals(other.fields[i])) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        return hash;
    }

}

这可以像这样使用:

public class SomeObject extends AbstractHashable {

    // both Foo and Bar have no mutable state
    private final Foo foo;
    private final Bar bar;

    public SomeObject(Foo foo, Bar bar) {
        super(foo, bar);
        this.foo = Objects.requireNonNull(foo);
        this.bar = bar; // null supported
    }

    // other methods, no equals or hashCode needed

}

这基本上是提出here提出的一些不同之处。

在我看来,这是一种简单而又好的方法,可以减少冗长,并且仍然可以有效地实现equalshashCode。但是,由于我不记得曾经见过类似的东西(除了上面的答案),我想特别询问是否有一些观点反对这种方法(或者可能有一些改进可以应用),在将其应用于整个项目之前。

4 个答案:

答案 0 :(得分:5)

我已经看到这种方法存在两个问题:

  1. 当且仅当所有字段都匹配时,才会认为两个对象相等。在现实世界中并非总是如此。
  2. 通过extend制作课程AbstractHashable,您不能再extend来自任何其他课程。重新使用equalshashCode
  3. 付出的代价非常高

    第一个问题可以通过将更多元数据传递到AbstractHashable类来解决,该类允许它识别哪些字段是可选的。例如,您可以将另一个数组传递给AbstractHashTable,其中包含要通过setter忽略的索引位置作为其元素。第二个问题可以通过使用 Composition 来解决。而不是扩展AbstractHashTable,重构它以便它可以与其用户建立 HAS-A 关系,而不是 IS-A 关系。

      

    然而,由于我不记得曾经见过类似的东西(除了上面连接的答案),我想特别询问是否有一些反对这种方法的观点

    这种方法肯定会影响代码的可读性方面。如果你能提出一种更具可读性的方法(比如使用注释),我就不会发现想要重用equalshashCode实现有什么问题。

    总而言之,像eclipse这样的现代IDE很容易为你生成equalshashCode实现,所以真的需要提出这种解决方案吗?我相信没有

答案 1 :(得分:0)

根据我的经验,这似乎很糟糕,因为你会增加你在运行时和编译时可以得到的错误(记住在列表等中使用这些对象现在可以给你带来惊喜的行为)。如果类中的字段顺序不同怎么办?其次你滥用继承吗?也许选择一些框架(https://projectlombok.org/)生成哈希码并基于注释等于?

答案 2 :(得分:0)

解决方案应该有效。

然而,从OOP的角度来看,感觉有点弱:

  • 您正在复制数据(超类中的字段与对象中的字段),因此您的对象是两倍大;如果你需要让它们变得可变,你需要在两个地方维持状态
  • 强制类层次结构,仅提供equals / hashCode

现代IDE很容易生成这样的方法,如果你想要你也可以使用框架(如Lombok)或库(如Guava&#39;对象/ Java 8&#39;对象)来简化这些方法。

答案 3 :(得分:0)

我建议您查看apache的HashCodeBuilderEqualsBuilder类。它还有基于反射的方法,如reflectionHashCode和reflectionEquals。