我default
在接口中创建了用于实现equals(Object)
和hashCode()
可预测的方法。我使用反射来迭代类型(类)中的所有字段以提取值并进行比较。该代码依赖于 Apache Commons Lang 及其HashCodeBuilder
和EqualsBuilder
.
问题是我的测试告诉我,当我第一次调用这些方法时,第一次调用会花费更多时间。计时器使用System.nanoTime()
. 以下是日志中的一个示例:
Time spent hashCode: 192444
Time spent hashCode: 45453
Time spent hashCode: 48386
Time spent hashCode: 50951
实际代码:
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();
}
}
这些方法如何用于实现hashCode
和equals
:
@Override
public int hashCode() {
return getHashCode(this);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Step && isEqual(this, obj);
}
测试示例:
@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) );
}
将这些方法放在接口中的原因是尽可能灵活。我的一些类可能需要继承。
我的测试表明我可以相信 hashcode 和 equals 总是为具有相同内部状态的对象返回相同的值。
我想知道的是我是否遗漏了什么。如果这些方法的行为是可以信任的?(我知道Lombok和AutoValue项目为实现这些方法提供了一些帮助,但我的客户不太热衷于这些库)。
关于为什么第一次执行方法调用总是需要大约 5 倍的时间的任何见解也将非常有帮助。