简体   繁体   中英

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.

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 . 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. This is quite a high price to be paying for reusing equals and hashCode .

The first issue could be solved by passing more meta data to your AbstractHashable class that allows it to identify what fields are optional. For instance, you could pass another array to AbstractHashTable that contains index positions to be ignored as its elements via a setter. The second issue can be solved by using 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.

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.

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? 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?

The solution should work.

However it feels a bit weak from OOP point of view:

  • 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

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.

I would suggest to have a look at apache's HashCodeBuilder and EqualsBuilder classes. It has reflection based methods like reflectionHashCode and reflectionEquals as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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