コレクションのhashCodeメソッドの最適な実装


299

hashCode()コレクションのメソッドの最適な実装をどのように決定しますか(equalsメソッドが正しくオーバーライドされていると想定)。


2
Java 7以降ではObjects.hashCode(collection)、完璧な解決策になるはずです。
ディアブロ

3
その方法は、単純に戻って-私は全くその答えに質問を考えていない@Diablo collection.hashCode()hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/...
cbreezier

回答:


438

最高の実装?これは使用パターンに依存するため、難しい質問です。

ほぼすべての場合に適した適切な実装が、項目8(第2版)のJosh BlochEffective Javaで提案されました。著者がそこにアプローチがなぜ良いのかを説明しているので、そこを調べるのが一番です。

ショートバージョン

  1. を作成し、ゼロ以外のint resultを割り当てます。

  2. 以下のためにあらゆる分野 fでテストequals()方法、ハッシュコードを計算するcことで:

    • フィールドfがaの場合boolean:計算し(f ? 0 : 1)ます。
    • フィールドfがある場合はbytecharshortまたはint:計算(int)f;
    • フィールドfがaの場合long:計算し(int)(f ^ (f >>> 32))ます。
    • フィールドfがaの場合float:計算しFloat.floatToIntBits(f)ます。
    • フィールドfがaの場合doubleDouble.doubleToLongBits(f)すべてのlong値のように戻り値を計算して処理します。
    • フィールドfがオブジェクトの場合hashCode()メソッドの結果を使用しf == nullます。
    • フィールドfが配列の場合:すべてのフィールドを個別の要素として参照し、再帰的にハッシュ値を計算し、次に説明するように値を結合します。
  3. ハッシュ値を以下cと組み合わせますresult

    result = 37 * result + c
  4. 戻る result

これにより、ほとんどの使用状況でハッシュ値が適切に分散されます。


45
ええ、37という数字がどこから来たのか特に気になります。
Kip

17
Josh Blochの「Effective Java」の本の項目8を使用しました。
dmeister

39
@dma_k素数とこの回答で説明されている方法を使用する理由は、計算されたハッシュコードが一意になるようにするためです。非素数を使用する場合、これを保証することはできません。どの素数を選択するかは関係ありません。37という数字に魔法はありません(42は素数ではありませんか?)
Simon Forsberg、2013

34
@SimonAndréForsbergええと、計算されたハッシュコードは常に一意であるとは限りません:)ハッシュコードです。しかし、私は考えをつかみました:素数には乗数が1つしかありませんが、素数には少なくとも2つあります。これにより、乗算演算子が同じハッシュを生成する、つまり衝突を引き起こすための追加の組み合わせが作成されます。
dma_k 2013

14
Bloch は最適化が簡単なため37ではなく31倍になると思います。
ルフィン

140

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)が必要ですが、同じように動作します。


8
これらを使用しない正当な理由がない限り、必ずこれらを使用してください。(IMHOを定式化する必要があるため、より強力に定式化します。)標準の実装/ライブラリを使用する場合の一般的な引数が適用されます(ベストプラクティス、十分にテストされ、エラーが発生しにくいなど)。
キサキ2014年

7
@ justin.hugheyあなたは混乱しているようです。オーバーライドする必要がある唯一のケースhashCodeは、カスタムがある場合equalsであり、それがまさにこれらのライブラリメソッドの目的です。ドキュメントは、との関連での動作が非常に明確equalsです。ライブラリの実装は、正しいhashCode実装の特性を知ることからあなたを免除することを主張するものではありません。これらのライブラリにより、がオーバーライドされるほとんどの場合に、このような適合実装を簡単に実装できequalsます。
bacar 2014年

6
java.util.Objectsクラスを見るすべてのAndroid開発者にとって、これはAPI 19でのみ導入されたので、KitKat以上で実行していることを確認してください。そうしないと、NoClassDefFoundErrorが返されます。
Andrew Kelly、

3
例としてjava.util.Objects.hash(...)、グアバcom.google.common.base.Objects.hashCode(...)方式ではなくJDK7 方式を選択した方がいいですが、ベストアンサーのIMO 。ほとんどの人は、追加の依存関係よりも標準ライブラリを選択すると思います。
Malte Skoruppa、2015年

2
2つ以上の引数があり、それらのいずれかが配列である場合hashCode()、配列は単なるであるため、結果は期待どおりにならない可能性がありますjava.lang.System.identityHashCode(...)
starikoff

59

Eclipseが提供する機能を使用することをお勧めします。これはかなり良い仕事をしており、ビジネスロジックの開発に努力とエネルギーを注ぐことができます。


4
+1優れた実用的なソリューション。dmeisterのソリューションはより包括的ですが、自分でハッシュコードを書き込もうとすると、nullを処理するのを忘れがちです。
Quantum7 2011

1
+1 Quantum7に同意しますが、Eclipseで生成された実装が実行していること、およびEclipseがその実装の詳細をどこから取得するかを理解することも本当に良いと思います。
jwir3 2014年

15
申し訳ありませんが、「[一部のIDE]によって提供される機能」に関する回答は、プログラミング言語のコンテキスト全体ではあまり関係がありません。何十ものIDEがあり、これは質問に答えません...つまり、これはアルゴリズム決定に関するものであり、equals()実装に直接関連付けられているためです-IDEは何も知りません。
ダレルティーグ

57

これはAndroidドキュメント(ウェイバックマシン)Github上の私の独自のコードにリンクされていますが、一般的にJavaで動作します。私の答えは、dmeisterのAnswerを拡張したものであり、コードを読みやすく、理解しやすくなっています。

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

1
Androidドキュメントに上記のコードが含まれなくなったため、Wayback Machine
-Android

17

最初に、equalsが正しく実装されていることを確認してください。IBM developerWorksの記事

  • 対称性:2つの参照aとbの場合、b.equals(a)の場合のみ、a.equals(b)
  • 反射性:すべてのnull以外の参照の場合、a.equals(a)
  • 推移性:a.equals(b)およびb.equals(c)の場合、a.equals(c)

次に、hashCodeとの関係が連絡先を尊重することを確認します(同じ記事から):

  • hashCode()との整合性:2つの等しいオブジェクトは同じ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と等しくなければならないという意味ではありません


3
もしそうなら(A != B) and (A.hashcode() == B.hashcode())、それは私たちがハッシュ関数衝突と呼んでいるものです。これは、ハッシュ関数のコドメインが常に有限であるのに対し、ドメインは通常有限ではないためです。コドメインが大きいほど、衝突の発生頻度は低くなります。優れたハッシュ関数は、特定のコドメインサイズが与えられた場合に達成できる可能性が最も高い、異なるオブジェクトに対して異なるハッシュを返す必要があります。ただし、完全に保証されることはほとんどありません。
KrzysztofJabłoński2013

これは、上記のGreyへの投稿に対するコメントにすぎません。良い情報ですが、質問の答えにはなりません
Christopher Rucinski

良いコメントですが、「異なるオブジェクト」という用語の使用には注意してください... equals()とhashCode()の実装は、必ずしもOOコンテキスト内の異なるオブジェクトに関するものではなく、通常、ドメインモデル表現(たとえば、2つの国コードと国IDを共有している場合、人々は同じであると見なすことができます-これらは、JVMの2つの異なる「オブジェクト」である可能性があります-「等しい」と見なされ、特定のハッシュコードを持っていると見なされます)...
Darrell Teague

7

Eclipseを使用する場合、以下を生成equals()してhashCode()使用できます。

ソース-> hashCode()およびequals()を生成します。

この関数を使用すると、等価性とハッシュコードの計算に使用するフィールドを決定でき、Eclipseは対応するメソッドを生成します。


7

良いの実装があります効果的なJavaのhashcode()equals()では、ロジックのApache CommonsのラングはHashCodeBuilderEqualsBuilderをチェックアウトします


1
このAPIの欠点は、equalsとハッシュコードを呼び出すたびにオブジェクト構築のコストを支払うことです(オブジェクトが不変であり、ハッシュを事前計算しない場合)。
James McMahon、

最近まで、これは私のお気に入りのアプローチでした。SharedKey OneToOne関連付けの基準を使用しているときにStackOverFlowErrorに遭遇しました。さらに、ObjectsクラスはJava7以降のhash(Object ..args)equals()メソッドを提供します。これらは、jdk 1.7+を使用するすべてのアプリケーションに推奨されます
Diablo

@Diablo私の推測では、問題はオブジェクトグラフのサイクルであり、参照を無視したり、サイクルを中断したりする必要があるため、ほとんどの実装ではうまくいきませんIdentityHashMap。FWIW私はすべてのエンティティにidベースのhashCodeおよびequalsを使用しています。
maaartinus

6

(コードの観点から)他のより詳細な回答を完了するための簡単なメモ:

質問how-do-i-create-a-hash-table-in-java、特にjGuru FAQエントリを検討すると、ハッシュコードを判断できる他の基準がいくつかあります。

  • 同期(アルゴは同時アクセスをサポートしているかどうか)?
  • フェイルセーフ反復(アルゴリズムは反復中に変化するコレクションを検出します)
  • null値(ハッシュコードはコレクション内のnull値をサポートしていますか)

4

私があなたの質問を正しく理解している場合、カスタムコレクションクラス(つまり、Collectionインターフェイスから拡張された新しいクラス)があり、hashCode()メソッドを実装したいと考えています。

コレクションクラスが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をスキップしてください。醜いです。)


3
バグはabout8.blogspot.comによる長い答えの中にあります。文字列の連結からハッシュコードを取得すると、同じ文字列になるすべての文字列の組み合わせに対して同じハッシュ関数が得られます。
SquareCog 2008

1
これはメタディスカッションであり、質問とはまったく関係ありませんか?;-)
ハッピー2008

1
これは、かなり重大な欠陥がある提案された回答に対する修正です。
SquareCog 2008

これは非常に限られた実装です
Christopher Rucinski

実装によって問題が回避され、別の問題が発生します。スワッピングfoobar同じにつながりhashCodeます。あなたのtoString私の知る限りではコンパイルされませんし、それがない場合、それは非効率的なひどいです。のようなもの109 * getFoo().hashCode() + 57 * getBar().hashCode()はより速く、よりシンプルで、不必要な衝突を引き起こしません。
maaartinus

2

コレクションを具体的に求めたので、他の回答ではまだ触れていない側面を追加したいと思います。HashMapは、コレクションに追加されたキーがハッシュコードを変更することを期待していません。全体の目的を打ち負かすだろう...


2

Apache Commons EqualsBuilderおよびHashCodeBuilderでリフレクションメソッドを使用します。


1
これを使用する場合は、リフレクションはコストがかかることに注意してください。私は正直に、これを使い捨てコード以外に使用しません。
James McMahon、

2

Arrays.deepHashCode(...)パラメータとして提供された配列を正しく処理するため、小さなラッパーを使用しています

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


1

私は、コードをクリーンに保つのに役立つ、ObjectsクラスのGoogleコレクションlibのユーティリティメソッドを使用することを好みます。多くの場合equalshashcodeメソッドはIDEのテンプレートから作成されるため、読みやすくありません。


1

これは、スーパークラスロジックを考慮した別の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());
    }

}

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の倍数は2を失うなど)。任意の奇数乗数が使用可能です。乗算器が小さいと計算が高速になる可能性があります(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でよく議論されています。


1
これは、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()が2つのオブジェクトに対してtrueを返す場合、hashCode()は同じ値を返す必要があります。equals()がfalseを返す場合、hashCode()は異なる値を返す必要があります。


1
SquareCogのようにすでに気づいています。ハッシュコードが2つの文字列の連結から1回生成されると、大量の衝突を生成することが非常に簡単になります("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")。ひどい傷です。両方のフィールドのハッシュコードを評価してから、それらの線形結合を計算することをお勧めします(できれば素数を係数として使用します)。
KrzysztofJabłoński2013

@KrzysztofJabłońskiそうです。また、スワップfoobarあまりにも、不必要な衝突を生成します。
maaartinus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.