컬렉션의 hashCode 메서드에 대한 최상의 구현
hashCode()
컬렉션에 가장 적합한 메서드 구현을 결정하려면 어떻게해야합니까 (메소드와 같은 것이 올바르게 재정의되었다고 가정)?
최고의 구현? 사용법 패턴에 따라 다르므로 어려운 질문입니다.
거의 모든 경우에 합리적인 구현이 Josh Bloch 의 효과적인 Java 항목 8 (제 2 판)에서 제안되었습니다 . 가장 좋은 방법은 저자가 접근 방식이 좋은 이유를 설명하기 때문에 거기서 찾아 보는 것입니다.
짧은 버전
a를 작성하고 0이 아닌 값을
int result
지정 하십시오 .들어 모든 분야
f
에서 테스트equals()
방법의 해시 코드를 계산c
하여 :- 필드 f가 a 인 경우
boolean
: 계산(f ? 0 : 1)
; - 필드 F 인 경우
byte
,char
,short
또는int
: 계산(int)f
; - 필드 f가 a 인 경우
long
: 계산(int)(f ^ (f >>> 32))
; - 필드 f가 a 인 경우
float
: 계산Float.floatToIntBits(f)
; - 필드 f가 a 인 경우
double
:Double.doubleToLongBits(f)
모든 긴 값과 같이 리턴 값을 계산 하고 처리하십시오. - 필드 f가 객체 인 경우 :
hashCode()
메소드 의 결과를 사용 하거나 0이면f == null
; - 필드 f가 배열 인 경우 : 모든 필드를 개별 요소로보고 해시 값을 재귀적인 방식으로 계산하고 다음에 설명 된대로 값을 결합하십시오.
- 필드 f가 a 인 경우
해시 값
c
을result
다음 과 결합하십시오 .result = 37 * result + c
반환
result
이로 인해 대부분의 사용 상황에 적절한 해시 값이 분배됩니다.
dmeister가 권장하는 효과적인 Java 구현에 만족하면 직접 롤링하는 대신 라이브러리 호출을 사용할 수 있습니다.
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
여기에는 Guava ( com.google.common.base.Objects.hashCode
) 또는 Java 7 ( java.util.Objects.hash
) 의 표준 라이브러리가 필요 하지만 같은 방식으로 작동합니다.
꽤 좋은 일을하는 Eclipse가 제공하는 기능을 사용하는 것이 좋으며 비즈니스 로직을 개발하는 데 노력과 에너지를 투입 할 수 있습니다.
이것은 Android
문서 (Wayback Machine) 및 Github의 내 코드 와 연결되어 있지만 일반적으로 Java에서 작동합니다. 내 대답은 읽고 이해하기 훨씬 쉬운 코드 로 dmeister의 답변 을 확장 한 것입니다.
@Override
public int hashCode() {
// Start with a non-zero constant. Prime is preferred
int result = 17;
// Include a hash for each field.
// Primatives
result = 31 * result + (booleanField ? 1 : 0); // 1 bit » 32-bit
result = 31 * result + byteField; // 8 bits » 32-bit
result = 31 * result + charField; // 16 bits » 32-bit
result = 31 * result + shortField; // 16 bits » 32-bit
result = 31 * result + intField; // 32 bits » 32-bit
result = 31 * result + (int)(longField ^ (longField >>> 32)); // 64 bits » 32-bit
result = 31 * result + Float.floatToIntBits(floatField); // 32 bits » 32-bit
long doubleFieldBits = Double.doubleToLongBits(doubleField); // 64 bits (double) » 64-bit (long) » 32-bit (int)
result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));
// Objects
result = 31 * result + Arrays.hashCode(arrayField); // var bits » 32-bit
result = 31 * result + referenceField.hashCode(); // var bits » 32-bit (non-nullable)
result = 31 * result + // var bits » 32-bit (nullable)
(nullableReferenceField == null
? 0
: nullableReferenceField.hashCode());
return result;
}
편집하다
일반적으로을 무시하면을 (를) 재정의 hashcode(...)
하려고합니다 equals(...)
. 따라서 이미 구현했거나 이미 구현 한 사람들을 위해 equals
여기 Github에서 좋은 참조가 있습니다 ...
@Override
public boolean equals(Object o) {
// Optimization (not required).
if (this == o) {
return true;
}
// Return false if the other object has the wrong type, interface, or is null.
if (!(o instanceof MyType)) {
return false;
}
MyType lhs = (MyType) o; // lhs means "left hand side"
// Primitive fields
return booleanField == lhs.booleanField
&& byteField == lhs.byteField
&& charField == lhs.charField
&& shortField == lhs.shortField
&& intField == lhs.intField
&& longField == lhs.longField
&& floatField == lhs.floatField
&& doubleField == lhs.doubleField
// Arrays
&& Arrays.equals(arrayField, lhs.arrayField)
// Objects
&& referenceField.equals(lhs.referenceField)
&& (nullableReferenceField == null
? lhs.nullableReferenceField == null
: nullableReferenceField.equals(lhs.nullableReferenceField));
}
먼저 equals가 올바르게 구현되었는지 확인하십시오. 에서 IBM의 developerWorks 기사 :
- 대칭 : a와 b, a.equals (b), b.equals (a)의 경우
- 반사성 : null이 아닌 모든 참조의 경우 a.equals (a)
- 전이성 : aequals (b) 및 b.equals (c)이면 a.equals (c)
그런 다음 hashCode와의 관계가 동일한 기사의 연락처를 존중하는지 확인하십시오.
- hashCode ()와의 일관성 : 두 개의 동일한 객체는 동일한 hashCode () 값을 가져야합니다.
마지막으로 좋은 해시 함수는 이상적인 해시 함수 에 접근하기 위해 노력해야 합니다 .
about8.blogspot.com, 당신은 말했다
두 객체에 대해 equals ()가 true를 반환하면 hashCode ()는 동일한 값을 반환해야합니다. equals ()가 false를 반환하면 hashCode ()는 다른 값을 반환해야합니다
나는 당신에 동의 할 수 없습니다. 두 객체에 동일한 해시 코드가있는 경우 동일한 해시 코드를 의미 할 필요는 없습니다.
A가 B와 같으면 A.hashcode는 B.hascode와 같아야합니다.
그러나
A.hashcode가 B.hascode와 같다고해서 A가 B와 같아야한다는 의미는 아닙니다.
이클립스를 사용하는 경우 다음을 생성 equals()
하고 hashCode()
사용할 수 있습니다 .
소스-> hashCode () 및 equals () 생성
이 함수 를 사용하면 동등성 및 해시 코드 계산에 사용할 필드 를 결정할 수 있으며 Eclipse는 해당 메소드를 생성합니다.
의 좋은 구현에있어 효과적인 자바 의 hashcode()
와 equals()
의 논리 아파치 코 몬즈 랭 . 체크 아웃 HashCodeBuilder 및 EqualsBuilder .
다른 더 자세한 답변을 완성하기위한 간단한 참고 사항 (코드 측면에서) :
내가 질문을 고려하면 방법 - 할 - 내가 만들 -에 - 자바 A-해시 테이블 특히 jGuru FAQ를 , 내가 해시 코드를 판단 할 수있는에 따라 다른 기준이 있습니다 생각 :
- 동기화 (알고는 동시 액세스를 지원합니까?)
- 안전 반복 실패 (알고가 반복 중에 변경되는 콜렉션을 감지 함)
- null 값 (해시 코드가 컬렉션에서 null 값을 지원함)
질문을 올바르게 이해하면 사용자 정의 컬렉션 클래스 (예 : Collection 인터페이스에서 확장되는 새 클래스)가 있고 hashCode () 메서드를 구현하려고합니다.
컬렉션 클래스가 AbstractList를 확장한다면 걱정할 필요가 없습니다. 모든 객체를 반복하고 hashCodes ()를 함께 추가하여 작동하는 equals () 및 hashCode () 구현이 이미 있습니다.
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
이제 원하는 것이 특정 클래스의 해시 코드를 계산하는 가장 좋은 방법이라면 일반적으로 ^ (비트 배타적 또는 연산자) 연산자를 사용하여 equals 메소드에서 사용하는 모든 필드를 처리합니다.
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
@ about8 : 거기에 꽤 심각한 버그가 있습니다.
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
동일한 해시 코드
당신은 아마 같은 것을 원할 것입니다
public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(요즘 Java에서 int에서 hashCode를 직접 얻을 수 있습니까? 자동 캐스팅을 수행한다고 생각합니다. 그런 경우 toString을 건너 뛰십시오.
컬렉션에 대해 구체적으로 요청한대로 다른 답변에서 아직 언급하지 않은 측면을 추가하고 싶습니다. HashMap은 컬렉션에 추가 된 키가 해시 코드를 변경하지 않을 것으로 예상합니다. 모든 목적을 이길 것입니다 ...
Apache Commons EqualsBuilder 및 HashCodeBuilder 에서 리플렉션 메소드를 사용하십시오 .
Arrays.deepHashCode(...)
매개 변수로 제공된 배열을 올바르게 처리하기 때문에 작은 래퍼를 사용 합니다.
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
가능한 범위에 해시 값을 균등하게 분배하는 모든 해싱 방법이 적합합니다. (유효 자바를 참조 http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result , 좋은 팁이있다) 해시 코드 구현을 위해 거기에 (항목 9 생각합니다 ...).
코드를 깨끗하게 유지하는 데 도움이 되는 클래스 객체의 Google 컬렉션 라이브러리에서 유틸리티 메소드를 사용 하는 것이 좋습니다. 매우 자주 equals
그리고 hashcode
메소드는 IDE의 템플릿으로 만들어 지므로 읽기가 깨끗하지 않습니다.
다음은 슈퍼 클래스 로직을 고려한 또 다른 JDK 1.7+ 접근 데모입니다. Object 클래스 hashCode () 계정, 순수한 JDK 종속성 및 추가 수동 작업이없는 것이 매우 편리합니다. 참고하시기 바랍니다 Objects.hash()
널 (null) 허용입니다.
나는 어떤 equals()
구현도 포함하지 않았지만 실제로는 당신이 그것을 필요로 할 것입니다.
import java.util.Objects;
public class Demo {
public static class A {
private final String param1;
public A(final String param1) {
this.param1 = param1;
}
@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
this.param1);
}
}
public static class B extends A {
private final String param2;
private final String param3;
public B(
final String param1,
final String param2,
final String param3) {
super(param1);
this.param2 = param2;
this.param3 = param3;
}
@Override
public final int hashCode() {
return Objects.hash(
super.hashCode(),
this.param2,
this.param3);
}
}
public static void main(String [] args) {
A a = new A("A");
B b = new B("A", "B", "C");
System.out.println("A: " + a.hashCode());
System.out.println("B: " + b.hashCode());
}
}
표준 구현은 약하며이를 사용하면 불필요한 충돌이 발생합니다. 상상해보십시오
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
지금,
new ListPair(List.of(a), List.of(b, c))
과
new ListPair(List.of(b), List.of(a, c))
같은이 hashCode
즉, 31*(a+b) + c
승수가 사용 List.hashCode
여기에 다시 사용됩니다. 분명히 충돌은 피할 수 없지만 불필요한 충돌을 일으키는 것은 단지 ... 불필요합니다.
을 사용하는 것이 현명한 것은 아닙니다 31
. 승수는 정보 손실을 피하기 위해 홀수 여야합니다 (승수조차도 가장 중요한 비트를 잃고 4의 배수는 2를 잃는 등). 홀수 승수를 사용할 수 있습니다. 작은 승수는 더 빠른 계산으로 이어질 수 있지만 (JIT는 시프트 및 덧셈을 사용할 수 있음), 현대 인텔 / AMD에서 곱셈의 대기 시간이 3주기에 불과하다는 점은 중요하지 않습니다. 작은 승수는 또한 작은 입력에 대해 더 많은 충돌을 일으켜 때로는 문제가 될 수 있습니다.
소수는 링 Z / (2 ** 32)에서 의미가 없으므로 소수를 사용하는 것은 의미가 없습니다.
따라서 무작위로 선택된 큰 홀수를 사용하는 것이 좋습니다. i86 / amd64 CPU는 단일 부호있는 바이트에 맞는 피연산자에 대해 더 짧은 명령어를 사용할 수 있으므로 109와 같은 곱셈기에는 약간의 속도 이점이 있습니다. 충돌을 최소화하려면 0x58a54cf5와 같은 것을 사용하십시오.
다른 장소에서 다른 승수를 사용하면 도움이되지만 추가 작업을 정당화하기에는 충분하지 않을 수 있습니다.
해시 값을 결합 할 때 일반적으로 boost c ++ 라이브러리에서 사용되는 결합 방법을 사용합니다.
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
이것은 균등 한 분배를 보장하는 상당히 좋은 일입니다. 이 수식의 작동 방식에 대한 자세한 내용은 StackOverflow 게시물 : boost :: hash_combine의 매직 번호를 참조하십시오.
http://burtleburtle.net/bob/hash/doobs.html 에서 다양한 해시 함수에 대한 좋은 토론이 있습니다.
간단한 클래스의 경우 equals () 구현으로 확인되는 클래스 필드를 기반으로 hashCode ()를 구현하는 것이 가장 쉬운 경우가 많습니다.
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
가장 중요한 것은 hashCode ()와 equals ()의 일관성을 유지하는 것입니다. 두 객체에 대해 equals ()가 true를 반환하면 hashCode ()는 동일한 값을 반환해야합니다. equals ()가 false를 반환하면 hashCode ()는 다른 값을 반환해야합니다.
참고 URL : https://stackoverflow.com/questions/113511/best-implementation-for-hashcode-method-for-a-collection
'IT' 카테고리의 다른 글
자식 무시 예외 (0) | 2020.03.19 |
---|---|
점이 원 안에 있는지 테스트하기위한 방정식 (0) | 2020.03.19 |
Eclipse에서 작업 공간을 삭제하는 방법? (0) | 2020.03.19 |
MySQL 데이터베이스에 연결할 때 SSL 연결에 대한 경고 (0) | 2020.03.19 |
LINQ를 사용하여 "not in"쿼리를 어떻게 수행 하시겠습니까? (0) | 2020.03.19 |