我已经在接口中创建了
问题是我的测试结果显示,第一次调用这些方法时,第一次调用需要更长时间。计时器使用
实际代码为:
这些方法如何用于实现
将这些方法放在接口中的原因是尽可能灵活。我的一些类可能需要继承。
我的测试表明,我可以相信hashcode和equals始终为具有相同内部状态的对象返回相同的值。
我想知道的是是否我遗漏了什么。这些方法的行为是否可靠?(我知道项目Lombok和AutoValue提供了一些帮助来实现这些方法,但我的客户不太喜欢这些库)。
任何关于为什么第一次执行方法调用总是需要大约5倍长的时间的见解也将非常有帮助。
默认
方法,用于以可预测的方式实现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倍长的时间的见解也将非常有帮助。
equals
和hashCode
不是类“域”的一部分,而是技术要求。通过使用default
方法,可以使用语法糖(混入)将纯技术要求添加到这些类中。编写implements HashAndEquals
比在每个类中复制和粘贴覆盖equals
和hashCode
要短得多(即DRY)。 - Alexander LangerObject.hashCode
和Object.equals
”。实际上,预期使用方式正是将equals
和hashCode
方法(调用default
方法的实现)复制并粘贴到每个单独的类中。从“接口”继承的方法不是用于从外部调用,而只是通过“接口”导出的实现工件,这是一种反模式。 - Holgerequals
/hashCode
,那么它可能被称为ValueType
,而关系,例如ComplexNumber
是一个ValueType
将证明继承的合理性。尽管如此,我仍然不建议使用默认的反射实现,但这并不是我所指的反模式。过去,interface
被滥用来声明常量,这些常量只有通过实现特定的interface
才能导入,以便在实现内部使用这些常量,就像这个问题的这些default
方法一样。 - HolgerisEqual
方法中,你有两个嵌套迭代,这意味着你正在执行n×m个操作。我建议坚持只有相同类的对象才能相等,因此,你只需要迭代一个Field
数组并比较两个实例在相同字段上的实际值即可。 - Holger