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.
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.
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.
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.
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.
I see two issues with this approach already :
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With