I have created default
methods in an interface for implementing equals(Object)
and hashCode()
in predictable manner. I use reflection to iterate all fields in a type (class) to extract the values and compare them. The code depends on Apache Commons Lang with its HashCodeBuilder
and EqualsBuilder
.
The thing is that my tests shows me that the first time I call on of these methods, it takes a lot more time in the first call. The timer uses System.nanoTime()
. Here is an example from the logs:
Time spent hashCode: 192444
Time spent hashCode: 45453
Time spent hashCode: 48386
Time spent hashCode: 50951
The actual code:
public interface HashAndEquals {
default <T> int getHashCode(final T type) {
final List<Field> fields = Arrays.asList(type.getClass().getDeclaredFields());
final HashCodeBuilder builder = new HashCodeBuilder(31, 7);
fields.forEach( f -> {
try {
f.setAccessible(true);
builder.append(f.get(type));
} catch (IllegalAccessException e) {
throw new GenericException(e.toString(), 500);
}
});
return builder.toHashCode();
}
default <T, K> boolean isEqual(final T current, final K other) {
if(current == null || other == null) {
return false;
}
final List<Field> currentFields = Arrays.asList(current.getClass().getDeclaredFields());
final List<Field> otherFields = Arrays.asList(other.getClass().getDeclaredFields());
final IsEqual isEqual = new IsEqual();
isEqual.setValue(true);
currentFields.forEach(c -> otherFields.forEach(o -> {
c.setAccessible(true);
o.setAccessible(true);
try {
if (o.getName().equals(c.getName())) {
if (!o.get(other).equals(c.get(current))) {
isEqual.setValue(false);
}
}
} catch (IllegalAccessException e) {
isEqual.setValue(false);
}
}));
return isEqual.getValue();
}
}
How these methods are used to implement hashCode
and equals
:
@Override
public int hashCode() {
return getHashCode(this);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Step && isEqual(this, obj);
}
Example of a test:
@Test
public void testEqualsAndHashCode() throws Exception {
Step step1 = new Step(1, Type.DISPLAY, "header 1", "description");
Step step2 = new Step(1, Type.DISPLAY, "header 1", "description");
Step step3 = new Step(2, Type.DISPLAY, "header 2", "description");
int times = 1000;
long total = 0;
for(int i = 0; i < times; i++) {
long start = System.nanoTime();
boolean equalsTrue = step1.equals(step2);
long time = System.nanoTime() - start;
total += time;
System.out.println("Time spent: " + time);
assertTrue( equalsTrue );
}
System.out.println("Average time: " + total / times);
for(int i = 0; i < times; i++) {
assertEquals( step1.hashCode(), step2.hashCode() );
long start = System.nanoTime();
System.out.println(step1.hashCode() + " = " + step2.hashCode());
System.out.println("Time spent hashCode: " + (System.nanoTime() - start));
}
assertFalse( step1.equals(step3) );
}
The reason for putting these methods in an interface is to be as flexible as possible. Some of my classes may need inheritance.
My test indicate that I can trust that both hashcode and equals always returns the same value for objects with the same internal state.
What I'd like to know is if I am missing something. And if the behavior of these methods can be trusted? (I know project Lombok and AutoValue offers some help to implement these methods, but my client are not too keen on these libraries).
Any insight as to why it always takes approximately 5 times longer to perform the method call the first time would also be very helpful.