简体   繁体   English

equals和hashCode的可重用实现

[英]Reusable implementation of equals and hashCode

I'm working on a project where many classes need proper typical implementations of equals and hashCode : each class has a set of final fields initialized at construction with "deeply" immutable objects ( null s are intended to be accepted in some cases) to be used for hashing and comparison. 我正在一个项目中,其中许多类需要equalshashCode适当典型实现:每个类具有一组最终字段,这些字段在构造时初始化为“深度”不可变的对象(在某些情况下,应接受null )用于哈希和比较。

To reduce the amount of boilerplate code, I thought about writing an abstract class providing common implementations of such behavior. 为了减少样板代码的数量,我考虑过编写一个提供此类行为常见实现的抽象类。

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

}

This is intended to be used like this: 打算这样使用:

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

}

This is basically what proposed here with some differences. 这基本上是这里提出的内容,但有一些差异。

This seems to me a straightforward yet good approach to reduce verbosity and still have efficient implementations of equals and hashCode . 在我看来,这是一种减少冗长但仍有效的equalshashCode实现的简单而有效的方法。 However, as I don't recall to have ever seen something similar (except in the answer linked above), I would like to specifically ask whether is there some point against this approach (or possibly some improvement which could be applied), before applying it across the whole project. 但是,由于我不记得曾经见过类似的东西(上面链接中的答案除外),所以我想特别问一下在应用之前是否有反对这种方法的观点(或可能会有所改进)它贯穿整个项目。

I see two issues with this approach already : 我已经看到此方法有两个问题:

  1. Two objects will be considered equal if and only if all their fields match. 当且仅当它们的所有字段都匹配时,两个对象才被视为相等。 This is not always the case in the real world. 在现实世界中,情况并非总是如此。
  2. By making your classes extend from AbstractHashable , you can no longer extend from any other classes. 通过使您的类从AbstractHashable extend ,您不再extend从任何其他类extend This is quite a high price to be paying for reusing equals and hashCode . 重用equalshashCode付出了很高的代价。

The first issue could be solved by passing more meta data to your AbstractHashable class that allows it to identify what fields are optional. 通过将更多的元数据传递给AbstractHashable类可以解决第一个问题,该类允许它标识哪些字段是可选的。 For instance, you could pass another array to AbstractHashTable that contains index positions to be ignored as its elements via a setter. 例如,您可以将另一个数组传递给AbstractHashTable ,该数组包含通过setter作为其元素被忽略的索引位置。 The second issue can be solved by using Composition . 第二个问题可以通过使用Composition解决。 Instead of extending AbstractHashTable , refactor it so that it can establish a HAS-A relationship with its users rather than an IS-A relationship. 对其进行重构,而不是扩展AbstractHashTable ,以便它可以与其用户建立HAS-A关系,而不是IS-A关系。

However, as I don't recall to have ever seen something similar (except in the answer linked above), I would like to specifically ask whether is there some point against this approach 但是,由于我不记得曾经见过类似的东西(上面链接中的答案除外),所以我想特别问一下这种方法是否有针对性

This approach will definitely impact the readability aspect of your code. 这种方法肯定会影响代码的可读性。 If you can come up with a more readable approach (say by using annotations), I don't see anything wrong with wanting to reuse equals and hashCode implementations. 如果您想出一种更具可读性的方法(例如通过使用批注),那么我想重用equalshashCode实现不会有任何问题。

That all being said, modern IDEs such as eclipse can easily generate the equals and hashCode implementation for you so is there really a need to come up with a solution of this sort? 综上所述,诸如eclipse之类的现代IDE可以轻松为您生成equalshashCode实现,因此,真的有必要提出这种解决方案吗? I believe no . 我相信不会

Based on my experience this seems bad as you will increase the error you can get in runtime vs. compile time (remember all use of these objects in lists etc. can now give you unexpexted behaviour). 根据我的经验,这似乎很糟糕,因为您会增加在运行时与编译时可能遇到的错误(记住,在列表等中对这些对象的所有使用。现在可以给您带来扩展的行为)。 What if the order of fields in the classes are different? 如果类中字段的顺序不同怎么办? Secondly are you misusing inheritance? 其次,您滥用继承吗? Maybee opt for some framework ( https://projectlombok.org/ ) which generate hashcode and equals based on annotation? Maybee选择某种框架( https://projectlombok.org/ ),该框架会基于注释生成哈希码和等号?

The solution should work. 该解决方案应该起作用。

However it feels a bit weak from OOP point of view: 但是,从OOP的角度来看,它有点弱:

  • you are duplicating data (fields in superclass vs field in object), so your object is twice as large; 您正在复制数据(超类中的字段与对象中的字段),因此您的对象大一倍; also if you ever need to make them mutable, you'd need to maintain state in two places 另外,如果您需要使它们可变,则需要在两个地方维护状态
  • the class hierarchy is forced, only to provide equals/hashCode 类层次结构被强制,仅提供equals / hashCode

Modern IDEs generate such methods quite easily, if you want you can also use frameworks (like Lombok) or libraries (like Guava's Objects/Java 8's Objects) to simplify these methods. 现代IDE很容易生成此类方法,如果您愿意,还可以使用框架(如Lombok)或库(如Guava的Objects / Java 8的Objects)来简化这些方法。

I would suggest to have a look at apache's HashCodeBuilder and EqualsBuilder classes. 我建议看看apache的HashCodeBuilderEqualsBuilder类。 It has reflection based methods like reflectionHashCode and reflectionEquals as well. 它也具有基于反射的方法,例如reflectionHashCode和reflectionEquals。

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

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