tho*_*s77 2 behavior equals hashcode java-8 default-method
我已经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
Run Code Online (Sandbox Code Playgroud)
实际代码:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
如何使用这些方法来实现hashCode和equals:
@Override
public int hashCode() {
return getHashCode(this);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Step && isEqual(this, obj);
}
Run Code Online (Sandbox Code Playgroud)
测试示例:
@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) );
}
Run Code Online (Sandbox Code Playgroud)
将这些方法放在界面中的原因是尽可能灵活.我的一些类可能需要继承.
我的测试表明我可以相信hashcode和equals总是为具有相同内部状态的对象返回相同的值.
我想知道的是,如果我遗失了什么.如果这些方法的行为可以信任?(我知道项目Lombok和AutoValue提供了一些实现这些方法的帮助,但我的客户对这些库并不太热衷).
任何关于为什么它总是花费大约5倍的时间来第一次执行方法调用的任何见解也将是非常有帮助的.
这里的方法没什么特别之处default.第一次在以前未使用的类上调用方法时,调用将触发类的类加载,验证和初始化,并且在JIT编译器/热点优化器启动之前,方法的执行将以解释模式启动.一个interface,它将被加载,并在实现它的类初始化时执行一些验证步骤,但是其他步骤仍然被推迟,直到它实际使用,在你的情况下调用一个default方法的时候interface第一次.
在Java中,正常现象是第一次执行比后续执行花费更多时间.在您的情况下,您正在使用lambda表达式,当在运行时生成功能接口实现时,这些表达式会有额外的第一次开销.
请注意,您的代码是一种常见的反模式,其存在时间比default方法长.有没有is-a的关系HashAndEquals和阶级"实行"它.您可以(并且应该)将这两个实用程序方法作为static方法提供给专用类,并且import static如果要在不预先声明声明类的情况下调用这些方法,则使用它们.
继承这些方法没有任何好处interface.毕竟,每个类都必须覆盖Object.hashCode并且Object.equals无论如何都可以故意选择是否使用这些实用方法.
| 归档时间: |
|
| 查看次数: |
5308 次 |
| 最近记录: |