270

최선의 구현 방법을 결정하는 방법hashCode()컬렉션에 대한 메서드 (equals 메서드가 올바르게 재정의되었다고 가정)?


  • Java 7 이상에서는Objects.hashCode(collection)완벽한 솔루션이어야합니다! - Diablo
  • @Diablo 나는 그 질문에 전혀 대답하지 않는다고 생각합니다 - 그 방법은 간단히 반환합니다.collection.hashCode()(jpg) - cbreezier

20 답변


408

최상의 구현? 그것은 사용 패턴에 의존하기 때문에 어려운 질문입니다.

거의 모든 경우에있어 합리적인조쉬 블로흐'에스효과적인 자바제 8 조 (제 2 판). 저자가 왜 접근법이 좋은지를 설명하기 때문에 가장 좋은 점은 그곳을 살펴 보는 것입니다.

짧은 버전

  1. 만들기int result그리고제로값.

  2. 에 대한모든 분야 f테스트를 거쳤습니다.equals()메소드, 해시 코드 계산c으로:

    • 필드 f가 a 인 경우boolean: 계산하다(f ? 0 : 1);
    • 필드 f가 a 인 경우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)모든 long 값처럼 반환 값을 처리합니다.
    • 필드 f가목적:의 결과를 사용하십시오.hashCode()메서드 또는 0 iff == null;
    • 필드 f가정렬: 모든 필드를 별도의 요소로보고 해시 값을 계산합니다.반복적 인 유행다음에 설명 된대로 값을 결합하십시오.
  3. 해시 값 결합cresult:

    result = 37 * result + c
    
  4. 반환result

대부분의 사용 상황에서 해시 값을 올바르게 분배해야합니다.


  • 그래, 특히 37 번이 어디서 왔는지 궁금해. - Kip
  • 나는 어떤 증거도 모른다. 37의 숫자는 임의적이지만 프라임이어야합니다. 왜? 확실하지는 않지만 모듈러 관절염과 분포를 유도하는 소수의 속성과 관련이 있습니다. - dmeister
  • Josh Bloch " Effective Java "의 항목 8을 사용했습니다. 책. - dmeister
  • @dma_k이 답변에 설명 된 소수와 그 사용 방법을 사용하는 이유는계산 된 해시 코드는 고유 할 것입니다.. 비 소수를 사용할 때이를 보장 할 수 없습니다. 어떤 소수의 숫자를 선택해도 문제가되지 않습니다. 숫자 37에 대해서는 마법 같은 것이 없습니다 (너무 나쁜 42는 소수가 아닙니다). - Simon Forsberg
  • @ SimonAndr &Forsberg 음, 계산 된 해시 코드는 항상 고유 할 수는 없습니다. 해시 코드입니다. 그러나 나는 생각을 가지고있다 : 소수는 소수가 적어도 두 개있다. 이는 곱셈 연산자가 동일한 해시, 즉 충돌을 유발하는 추가 조합을 만듭니다. - dma_k

121

dmeister가 권장하는 Effective 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)하지만 같은 방식으로 작동합니다.


  • 사람이 이들을 사용하지 않는 좋은 이유가 없다면, 어떤 경우에도 반드시 이것을 사용해야합니다. 표준 구현 / 라이브러리 사용에 대한 일반적인 논점이 적용됩니다 (모범 사례, 잘 테스트 된, 오류가 발생하기 쉬운 오류 등). - Kissaki
  • @ justin.hughey 당신은 혼란스러워 보입니다. 재정의해야하는 유일한 경우hashCode당신이 관례가 있다면equals, 그리고 그것은 정확하게 이러한 라이브러리 메소드가 설계된 것입니다. 문서는 자신의 행동과 관련하여 매우 분명합니다.equals. 라이브러리 구현은 올바른 것의 특성을 알지 못하게한다고 주장하지 않습니다.hashCode구현은 다음과 같습니다 -이 라이브러리는더 쉬운대부분의 경우에 이러한 준수 구현을 구현할 수 있습니다.equals재정의됩니다. - bacar
  • java.util.Objects 클래스를보고있는 Android 개발자는 API 19에서만 도입되었으므로 KitKat 이상에서 실행해야합니다. 그렇지 않으면 NoClassDefFoundError가 발생합니다. - Andrew Kelly
  • 최선의 대답은 IMO입니다. 예를 들어 JDK7을 선택했을 것입니다.java.util.Objects.hash(...)구아바보다 방법com.google.common.base.Objects.hashCode(...)방법. 나는 대부분의 사람들이 여분의 의존성을 넘어 표준 라이브러리를 선택할 것이라고 생각한다. - Malte Skoruppa
  • 인수가 두 개 이상이고 배열 중 하나라도 배열이면 결과가 예상과 다를 수 있습니다.hashCode()배열은java.lang.System.identityHashCode(...). - starikoff

59

이클립스가 제공하는 기능을 사용하는 것이 더 낫다. 비즈니스 로직을 개발하는 데 노력과 에너지를 투입 할 수있다.


  • +1 좋은 실용적인 솔루션. dmeister의 솔루션은 더 포괄적이지만 해쉬 코드를 직접 작성하려고 할 때 null을 처리하는 것을 잊어 버리는 경향이 있습니다. - Quantum7
  • +1 Quantum7에 동의하지만, Eclipse에서 생성 된 구현이 수행하는 작업과 구현 세부 사항을 얻는 위치를 이해하는 것이 정말 좋습니다. - jwir3
  • 죄송 합니다만 " [일부 IDE]에서 제공하는 기능 "에 대한 답변은 일반적으로 프로그래밍 언어와 관련이 없습니다. 수십개의 IDE가 있는데 이것은 질문에 대답하지 않습니다 ... 왜냐하면 이것이 알고리즘 결정과 직접적으로 equals () 구현과 관련되어 있기 때문입니다 - IDE가 아무것도 알지 못하는 것입니다. - Darrell Teague

53

이것은에 연결되어 있지만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여기에 좋은 참고 자료가있다.내 기둥에서...

@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));
}



17

먼저 equals가 올바르게 구현되었는지 확인하십시오. 에서IBM DeveloperWorks 기사:

  • 대칭 (symmetric) : 두 개의 참조 a와 b에 대해, a.equals (b) b.equals (a)
  • 반사성 : null 이외의 모든 참조의 경우, a.equals (a)
  • 전이성 : a.equals (b)와 b.equals (c)가있는 경우, a.equals (c)

그런 다음 hashCode와의 관계가 (동일한 아티클의) 연락처를 존중하는지 확인하십시오.

  • hashCode ()와의 일관성 : 두 개의 동일한 객체는 동일한 hashCode () 값을 가져야합니다.

마지막으로 좋은 해시 함수는이상적인 해시 함수.


11

about8.blogspot.com, 당신이 말했습니다.

equals ()가 2 개의 객체에 대해서 true를 돌려주는 경우, hashCode ()는 같은 값을 돌려 줄 필요가 있습니다. equals ()가 false를 반환하면 hashCode ()는 다른 값을 반환해야합니다.

나는 너에게 동의 할 수 없다. 2 개의 오브젝트가 같은 해시 코드를 가지는 경우, 그것들이 동일하다는 것을 의미 할 필요는 없다.

A가 B와 같으면 A.hashcode는 B.hascode와 같아야합니다.

그러나

A.hashcode가 B.hascode와 같으면 A가 반드시 B와 같아야 함을 의미하지는 않습니다.


  • 만약(A != B) and (A.hashcode() == B.hashcode()), 해시 함수 충돌이라고하는 것입니다. 해시 함수의 코드 도메인은 항상 유한하지만 도메인은 일반적으로 그렇지 않기 때문에 결과가 다릅니다. codomain이 클수록 충돌이 적게 발생합니다. 좋은 해시 함수는 특정 코드 영역 크기가 주어질 때 최대 가능성을 가진 다른 객체에 대해 다른 해시를 반환해야합니다. 그것은 거의 완벽하게 보장 할 수는 없습니다. - Krzysztof Jabłoński
  • 이것은 회색 위의 게시물에 대한 주석이어야합니다. 좋은 정보이지만 질문에 실제로 대답하지 않습니다. - Christopher Rucinski
  • 좋은 의견이지만 '다른 개체'라는 용어 사용에주의하십시오. 왜냐하면 equals ()와 hashCode () 구현은 객체 지향 문맥에서 서로 다른 객체에 관한 것은 아니지만 일반적으로 도메인 모델 표현에 더 가깝기 때문입니다 (예를 들어 두 사람은 국가 코드를 공유하면 동일하게 간주 될 수 있습니다. 국가 ID - JVM에서 서로 다른 두 개의 개체가 될 수 있지만 - 이들은 평등하다고 간주되어 주어진 hashCode가 있음) ... - Darrell Teague

7

이클립스를 사용한다면, 생성 할 수있다.equals()hashCode()사용 :

Source -> hashCode () 및 equals ()를 생성합니다.

이 기능을 사용하면어느 필드당신은 평등과 해시 코드 계산을 위해 사용하고, Eclipse는 상응하는 메소드를 생성한다.


7

좋은 구현 방법이 있습니다.효과적인 자바'에스hashcode()equals()논리아파치 커먼즈 랭. 점검HashCodeBuilderEqualsBuilder.


  • 이 API의 단점은 equals와 hashcode를 호출 할 때마다 객체 생성 비용을 지불한다는 것입니다 (객체가 변경되지 않고 해시를 미리 계산하지 않는 한). 특정 경우에 많이 발생할 수 있습니다. - James McMahon
  • 이것은 최근까지 제가 가장 좋아하는 방법이었습니다. SharedKey OneToOne 연관에 대한 기준을 사용하는 동안 StackOverFlowError를 만났습니다. 더 이상,Objects수업 제공hash(Object ..args)&equals()Java7의 메소드. JDK 1.7 이상을 사용하는 모든 응용 프로그램에 권장됩니다. - Diablo
  • @Diablo 여러분의 문제는 객체 그래프의 한주기였습니다. 그런 다음 참조를 무시하거나주기를 깨뜨리지 않아도 대부분의 구현에서는 운이 좋지 않을 것입니다.IdentityHashMap). FWIW 나는 모든 엔티티에 대해 id 기반 hashCode와 equals를 사용합니다. - maaartinus

4

다른보다 자세한 답변을 완성하기위한 간단한 참고 사항 (코드의 용어로) :

질문을 생각하면해시 테이블 - 인 - 자바 (How-do-i-create-a-hash-table-in-java)특히jGuru FAQ 항목, 해시 코드를 판단 할 수있는 다른 기준은 다음과 같습니다.

  • 동기화 (알 고가 동시 액세스를 지원하는지 여부)?
  • fail safe 반복 (algo가 반복 중에 변화하는 collection을 탐지 하는가?)
  • null 값 (해시 코드는 컬렉션에서 null 값을 지원합니까?)


  • 이상한 측면은 그다지 인상 지어지지 않았다. - Eugene

4

귀하의 질문을 올바르게 이해하면 맞춤 컬렉션 클래스 (Collection 인터페이스에서 확장되는 새로운 클래스)가 있고 hashCode () 메소드를 구현하고 싶습니다.

Collection 클래스가 AbstractList를 확장한다면, 걱정할 필요가 없습니다. equals ()와 hashCode ()의 구현은 이미 모든 객체를 반복하고 해시 코드 (hashCodes)를 함께 추가하여 작동합니다.

   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);
}


2

@ 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을 건너 뜁니다.)


  • 질문을 두 번이나 보았지만 거기에 버그를 찾지 못했습니다 ... - Huppie
  • 버그는 about8.blogspot.com의 긴 대답입니다. 문자열의 연결에서 해시 코드를 얻는 것은 동일한 문자열에 추가되는 문자열의 조합에 대해 동일한 해시 함수를 남깁니다. - SquareCog
  • 그래서 이것은 메타 토론이며 질문과 전혀 관련이 없습니까? ;-) - Huppie
  • 상당히 심각한 결함이있는 제안 된 답변에 대한 수정입니다. - SquareCog
  • 매우 제한된 구현입니다. - Christopher Rucinski

2

컬렉션에 대해 특별히 질문 했으므로 다른 답변에서 아직 언급하지 않은 부분을 추가하고 싶습니다. HashMap은 키가 컬렉션에 추가되면 해시 코드가 변경 될 것으로 기대하지 않습니다. 모든 목적을 이길 ...


2

Apache Commons에서 반사 방법 사용EqualsBuilderHashCodeBuilder.


  • 이것이 반사를 비싸다는 것을 명심하십시오 이것을 사용하기 위하여려고하는 경우에. 솔직히 코드를 버리는 것 외에는 아무 것도 사용하지 않을 것입니다. - James McMahon

1

가능한 범위에서 해시 값을 균등하게 분배하는 해싱 방법은 좋은 구현입니다. 효과적인 자바보기 (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), 좋은 팁 거기에 hashcode 구현 (item 9 i think ...).


1

나는 유틸리티 메소드를 사용하는 것을 선호한다.Object 컬렉션의 Google Collections lib내 코드를 깨끗하게 유지하는 데 도움이됩니다. 매우 자주equalshashcode메소드는 IDE의 템플릿에서 만들어 지므로 읽기에는 깨끗하지 않습니다.


1

나는 작은 래퍼를 사용한다.Arrays.deepHashCode(...)매개 변수로 제공된 배열을 올바르게 처리하기 때문에

public static int hash(final Object... objects) {
    return Arrays.deepHashCode(objects);
}


1

수퍼 클래스 로직이 설명 된 다른 JDK 1.7+ 접근 데모가 있습니다. Object 클래스 hashCode (), 순수한 JDK 종속성 및 추가 수동 작업이 없다는 것을 알았습니다. 주의 사항Objects.hash()무효입니다.

나는 어떤 것도 포함하지 않았다.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());
    }

}


1

표준 구현은 약하고 불필요한 충돌로 연결됩니다. 상상해보십시오.

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의 배수는 두 개를 잃습니다 등). 임의의 승수를 사용할 수 있습니다. 작은 곱셈기로 인해 더 빠른 계산이 가능합니다 (JIT는 쉬프트 및 덧셈을 사용할 수 있습니다). 그러나 곱셈은 현대 Intel / AMD에서 단지 3 사이클의 대기 시간을 갖기 때문에 이것은 중요하지 않습니다. 작은 승수로 인해 작은 입력에 대해 더 많은 충돌이 발생하기 때문에 때때로 문제가 될 수 있습니다.

반지름 Z / (2 ** 32)에서 소수는 의미가 없으므로 소수를 사용하는 것은 의미가 없습니다.

그래서 무작위로 선택된 큰 홀수를 사용하는 것이 좋습니다 (소수를 자유롭게 사용하십시오). i86 / amd64 CPU는 단일 부호있는 바이트에 피연산자를 맞추기 위해 더 짧은 명령어를 사용할 수 있기 때문에 109와 같은 곱셈기에 약간의 속도 이점이 있습니다. 충돌을 최소화하기 위해 0x58a54cf5와 같은 것을 가져옵니다.

다른 장소에서 다른 승수를 사용하는 것이 도움이되지만, 추가 작업을 정당화하기에는 충분하지 않을 수 있습니다.


0

해시 값을 결합 할 때, 나는 보통 boost C ++ 라이브러리에서 사용되는 결합 방법을 사용합니다 :

seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);

이는 균등 분배를 보장하는 훌륭한 역할을합니다. 이 수식이 어떻게 작동하는지에 대한 설명은 StackOverflow 게시물을 참조하십시오.boost :: hash_combine의 매직 넘버

다른 해시 함수에 대한 좋은 토론은 다음과 같습니다.http://burtleburtle.net/bob/hash/doobs.html


  • 이것은 C ++이 아니라 Java에 관한 질문입니다. - dano

-1

간단한 클래스의 경우, 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 ()는 다른 값을 반환해야합니다.


  • SquareCog처럼 이미 알아 챘습니다. 해시 코드가 두 문자열의 연결에서 한 번 생성되면 매우 많은 수의 충돌을 생성하기 쉽습니다.("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc"). 그것은 심각한 결함입니다. 두 필드에 대한 해시 코드를 평가 한 다음 이들의 선형 조합을 계산하는 것이 더 좋습니다 (바람직하게는 소수를 계수로 사용). - Krzysztof Jabłoński
  • @ KrzysztofJabłoński. 또한, 스와핑foobar불필요한 충돌을 일으킨다. - maaartinus

연결된 질문


관련된 질문

최근 질문