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) );
}
이러한 메서드를 인터페이스에 넣는 이유는 가능한 한 유연하기 때문입니다. 내 클래스 중 일부는 상속이 필요할 수 있습니다.
내 테스트에 따르면 해시 코드와 같음 모두 동일한 내부 상태를 가진 객체에 대해 항상 동일한 값을 반환한다고 믿을 수 있습니다.
내가 알고 싶은 것은 내가 뭔가를 놓치고 있는지입니다. 그리고 이러한 방법의 동작을 신뢰할 수 있다면? ( Lombok 및 AutoValue 프로젝트가 이러한 방법을 구현하는 데 도움이 된다는 것을 알고 있지만 제 클라이언트는 이러한 라이브러리에 너무 열중하지 않습니다).
메서드 호출을 처음 수행하는 데 항상 약 5 배 더 오래 걸리는 이유에 대한 통찰력도 매우 유용 할 것입니다.
여기에는 default
메서드에 특별한 것이 없습니다 . 이전에 사용되지 않은 클래스에서 처음으로 메소드를 호출하면 호출이 클래스로드, 확인 및 클래스 초기화를 트리거하고 메소드 실행은 JIT 컴파일러 / 핫스팟 최적화 프로그램이 시작되기 전에 해석 모드에서 시작됩니다. 의 메소드 가 호출 interface
될 때 실제로 사용될 때까지 다른 단계는 여전히 지연되고 있습니다 . 처음으로.default
interface
최초 실행이 후속 실행보다 더 많은 시간이 소요되는 것은 Java의 정상적인 현상입니다. 귀하의 경우 런타임에 기능적 인터페이스 구현이 생성 될 때 추가 첫 번째 오버 헤드가있는 람다 식을 사용하고 있습니다.
코드는 default
메서드 보다 오래 존재하는 일반적인 안티 패턴입니다 . 와 클래스 "구현" 사이 에는 is-a 관계 가 없습니다 HashAndEquals
. 이 두 유틸리티 메서드를 static
전용 클래스의 메서드로 대신 제공하고 import static
선언 클래스를 앞에 추가하지 않고 이러한 메서드를 호출하려는 경우 사용할 수 있습니다 (그리고 그래야합니다) .
.NET Framework에서 이러한 메서드를 상속해도 이점이 없습니다 interface
. 결국 각 클래스는 재정의해야 Object.hashCode
하며 Object.equals
어쨌든 이러한 유틸리티 메서드를 사용할지 여부를 신중하게 선택할 수 있습니다.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다