hashCode()/equals() using lambdas
Writing proper hashCode() and equals() methods is really important, but a pretty boring task. Luckily IDEs nowadays generate these, so we don’t have to worry about them too much. But this approach has two downsides:
- it adds at least 20 lines of complex looking code to often very simple classes (e.g. DTOs or JPA entities), which reduces readability and might have a negative impact on static code analysis
- the IDEs take different approaches, so the code looks different for e.g. Eclipse and IntelliJ
Lambdas as an alternative
To simplify the process of generating hashCode() and equals() methods I thought about using Java 8 lambdas. This is the approach I came up with:
public class HashCodeEqualsBuilder {
public static int buildHashCode(T t, Function<? super T, ?>... functions) {
final int prime = 31;
int result = 1;
for (Function<? super T, ?> f : functions) {
Object o = f.apply(t);
result = prime * result + ((o == null) ? 0 : o.hashCode());
}
return result;
}
public static boolean isEqual(T t, Object obj, Function<? super T, ?>... functions) {
if (t == obj) {
return true;
}
if (obj == null) {
return false;
}
if (t.getClass() != obj.getClass()) {
return false;
}
T other = (T) obj;
for (Function<? super T, ?> f : functions) {
Object own = f.apply(t);
Object foreign = f.apply(other);
if (own == null) {
if (foreign != null) {
return false;
}
} else if (!own.equals(foreign)) {
return false;
}
}
return true;
}
}
To use it in a class (e.g. DTO) type the following:
@Override
public int hashCode() {
return HashCodeEqualsBuilder.buildHashCode(this, MyClass::getA, MyClass::getB);
}
@Override
public boolean equals(Object obj) {
return HashCodeEqualsBuilder.isEqual(this, obj, MyClass::getA, MyClass::getB);
}
Based on some microbenchmarks I did, the performance is equal to the autogenerated implementation (e.g. by Eclipse). I will start using this from now on and can recommend using this approach for two reasons:
- it does not add additional lines of code to your project that might pop up as uncovered by unit tests
- updating the implementation (if ever) must only happen at a single place