Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 (nulls 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.

like image 756
rrobby86 Avatar asked Jan 25 '17 09:01

rrobby86


People also ask

How do you correctly override the hashCode () and equals () methods?

if a class overrides equals, it must override hashCode. when they are both overridden, equals and hashCode must use the same set of fields. if two objects are equal, then their hashCode values must be equal as well. if the object is immutable, then hashCode is a candidate for caching and lazy initialization.

What happens if we do not override hashCode () and equals () in HashMap?

If you don't override hashcode() then the default implementation in Object class will be used by collections. This implementation gives different values for different objects, even if they are equal according to the equals() method.

How hashCode () and equals () methods are used in HashMap?

In HashMap, hashCode() is used to calculate the bucket and therefore calculate the index. equals() method: This method is used to check whether 2 objects are equal or not. This method is provided by the Object class. You can override this in your class to provide your implementation.

Is it necessary to override hashCode and equals method in HashMap?

You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object. hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.


1 Answers

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.

like image 149
Chetan Kinger Avatar answered Oct 13 '22 15:10

Chetan Kinger