270

どのように我々はの最良の実装を決めるのですかhashCode()コレクションのメソッド(equalsメソッドが正しくオーバーライドされたと仮定して)


20 답변


408

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

ほぼすべての場合において、合理的で適切な実装が提案されています。ジョシュブロッホ効果的なJavaアイテム8(第2版)に。作者がそこでアプローチが良い理由を説明しているので、それをそこで調べることが最善です。

ショートバージョン

  1. を作成int resultそして割り当てるゼロでありません値。

  2. にとってあらゆる分野 fでテストequals()メソッド、ハッシュコードを計算cによって:

    • 体fがaの場合boolean: 計算する(f ? 0 : 1);
    • 体fがaの場合bytecharshortまたは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()methodまたは次の場合は0f == null;
    • 体fがアレイ:すべてのフィールドを別々の要素として見て、ハッシュ値を再帰的なファッション次に説明するように値を組み合わせます。
  3. ハッシュ値を組み合わせるcresult

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

これにより、ほとんどの使用状況でハッシュ値が正しく分散されるはずです。


  • そうです、37という数字がどこから来たのか、特に興味があります。 - Kip
  • 証明を知りません。 37の数は任意ですが、素数でなければなりません。どうして?よくわかりませんが、モジュロ関節炎と素数の性質が関係しているため、goの分布につながります。 - dmeister
  • Josh Blochの「Effective Java」の項目8を使用しました。本。 - dmeister
  • この答えで説明されている素数と方法を使用する理由は、計算されたハッシュコードは一意になります。素数でない数を使うとき、これを保証することはできません。どの素数を選択しても問題ありません。37という数字について不思議なことは何もありません(42が素数ではないのは残念です)。 - Simon Forsberg
  • @ SimonAndré Forsbergええと、計算されたハッシュコードは必ずしも一意であるとは限りません:)ハッシュコードです。しかし私は、素数には乗数が1つしかないのに対し、非素数には最低2があるという考えを得ました。それは、同じハッシュを生じる、すなわち衝突を引き起こすために乗算演算子のための余分な組み合わせを作り出す。 - dma_k

121

dmeisterによって推奨されているEffective Javaの実装に満足しているなら、独自のものを使う代わりにライブラリ呼び出しを使うことができます。

@Override
public int hashCode() {
    return Objects.hashCode(this.firstName, this.lastName);
}

これにはグアバが必要です(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
  • 例として、私はむしろJDK7を選択したほうがよいが、ベストアンサーIMOjava.util.Objects.hash(...)グアバよりも方法com.google.common.base.Objects.hashCode(...)方法。私はほとんどの人が特別な依存関係よりも標準ライブラリを選ぶだろうと思います。 - Malte Skoruppa
  • 2つ以上の引数があり、それらのいずれかが配列の場合、結果は期待したものとは異なる場合があります。hashCode()配列のためだけですjava.lang.System.identityHashCode(...)。 - starikoff

59

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


  • +1良い実用的な解決策。 dmeisterの解決策はより包括的ですが、自分でハッシュコードを書き込もうとしたときにnullを処理するのを忘れがちです。 - Quantum7
  • +1 Quantum7に同意するが、Eclipseで生成された実装が何をしているのか、そしてどこから実装の詳細を取得するのかを理解するのも本当に良いことだと思う。 - jwir3
  • 申し訳ありませんが、「[some IDE]によって提供される機能」に関する回答をご覧ください。一般的なプログラミング言語の文脈では実際には関連性がありません。何十ものIDEがありますが、これは質問に答えていません。つまり、これはアルゴリズムの決定に関するものであり、equals()の実装に直接関連しているからです。 - Darrell Teague

53

これはにリンクされていますがAndroidドキュメンテーション(Wayback Machine)そしてGithubの私自身のコード、それは一般的にJavaのために働くでしょう。私の答えはdmeister's 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));
}



17

まずequalsが正しく実装されていることを確認してください。からIBM DeveloperWorksの記事

  • 対称性:aとbの2つの参照の場合、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に等しくなければならないという意味ではありません。


  • もし(A != B) and (A.hashcode() == B.hashcode())これがハッシュ関数の衝突と呼ばれるものです。これは、ハッシュ関数のコドメインが常に有限であるのに対し、そのドメインは通常有限ではないためです。コドメインが大きいほど、衝突が発生する頻度は少なくなります。良いハッシュ関数は、特定のコドメインサイズを考えれば達成可能な最大の可能性で、異なるオブジェクトに対して異なるハッシュを返すべきです。それが完全に保証されることはめったにありません。 - Krzysztof Jabłoński
  • これはGreyへの上記記事へのコメントに過ぎません。良い情報ですが、実際には質問に答えていません - Christopher Rucinski
  • 良いコメントですが、「異なるオブジェクト」という用語の使用には注意が必要です。これは、equals()ひいてはhashCode()の実装が必ずしもオブジェクト指向コンテキスト内の異なるオブジェクトに関するものではなく、通常はドメインモデル表現に関するものであるためです(たとえば、2人が国コードを共有する場合は同じと見なすことができます)。国ID - これらはJVM内の2つの異なるオブジェクトである可能性がありますが、等しいと見なされ、指定されたハッシュコードを持っています)。 - Darrell Teague

7

あなたが日食を使用する場合は、あなたが生成することができますequals()そしてhashCode()を使用して:

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

この機能を使用してあなたが決めることができますどの分野等価性とハッシュコードの計算に使いたい場合は、Eclipseが対応するメソッドを生成します。


7

の良い実装があります効果的なJavahashcode()そしてequals()ロジックインApache Commons Lang。チェックアウトHashCodeBuilderそしてEqualsBuilder


  • このAPIの欠点は、equalsとhashcodeを呼び出すたびにオブジェクトを構築するためのコストがかかることです(オブジェクトが不変でハッシュを事前計算しない限り)。これは、場合によってはかなりの額になる可能性があります。 - James McMahon
  • 最近まで、これが私のお気に入りのアプローチでした。 SharedKey OneToOneアソシエーションの基準を使用しているときにStackOverFlowErrorに遭遇しました。もっとObjectsクラスが提供するhash(Object ..args)&equals()Java7以降のメソッドこれらはjdk 1.7+を使用するすべてのアプリケーションに推奨されます - Diablo
  • @Diablo私の推測では、あなたの問題はオブジェクトグラフのサイクルでしたが、参照を無視したりサイクルを中断したりする必要があるため、ほとんどの実装ではうまくいきません。IdentityHashMap)私はidベースのhashCodeを使用しており、すべてのエンティティに等しいです。 - maaartinus

4

(コードに関して)他のより詳細な答えを完成させるためのちょっとしたメモ:

私が質問を考えるならHow-do-i-Javaでハッシュテーブルを作成するそして特にjGuru FAQエントリーハッシュコードを判断できる他の基準は、次のとおりです。

  • 同期(algoは同時アクセスをサポートしているかどうか)
  • 安全な反復処理(反復処理中に変化するコレクションをアルゴが検出します)
  • null値(ハッシュコードはコレクション内のnull値をサポートしますか)


  • そのような重要な側面が支持されなかったことは奇妙です。 - Eugene

4

私があなたの質問を正しく理解したならば、あなたはカスタムコレクションクラス(すなわち、Collectionインターフェースから拡張する新しいクラス)を持っていて、そしてあなたはhashCode()メソッドを実行したいです。

コレクションクラスがAbstractListを拡張する場合は、心配する必要はありません。すべてのオブジェクトを繰り返し処理し、それらのhashCode()を一緒に追加することによって機能する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メソッドで使用するすべてのフィールドを処理するために通常^(bitwise exclusive or)演算子を使用します。

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をスキップしてください。見苦しいです。)


  • 質問を2回見ましたが、バグが見つかりませんでした... - Huppie
  • バグはabout8.blogspot.comによる長い答えの中にあります - 文字列の連結からハッシュコードを得ることは同じ文字列になる文字列のどんな組み合わせに対しても同じであるハッシュ関数をあなたに残します。 - SquareCog
  • それで、これはメタディスカッションであり、質問とはまったく関係ありませんか? ;-) - Huppie
  • これは、提案された回答に対する訂正であり、かなり重大な欠陥があります。 - SquareCog
  • これは非常に限られた実装です。 - Christopher Rucinski

2

あなたが特にコレクションを要求したとき、私は他の答えがまだ述べていないという側面を加えたいと思います:HashMapは彼らがコレクションに加えられたら彼らのキーが彼らのハッシュコードを変えることを期待しません。全体の目的を打ち負かすだろう...


2

Apache Commonsでリフレクションメソッドを使うEqualsBuilderそしてHashCodeBuilder


  • あなたが使用しようとしているならこれは反射が高価であることに注意してください。コードを捨てる以外には、これを使用しないでください。 - James McMahon

1

ハッシュ値を可能な範囲に均等に分配するハッシング方法は良い実装です。有効なjavaを参照してください(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私は思う...)。


1

ユーティリティメソッドfrommを使うのが好きです。Google Collections libクラスのObjectsからそれは私が私のコードをきれいに保つのを助けます。よくequalsそしてhashcodeメソッドはIDEのテンプレートから作られているので、読みやすくはありません。


1

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

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


1

これはスーパークラスロジックを考慮したJDK 1.7以降のアプローチのもう1つのデモンストレーションです。 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の倍数で2が失われるなど)。任意の奇数乗数が使用可能です。小さい乗数はより速い計算につながるかもしれません(JITはシフトと加算を使うことができます)が、乗算が現代のIntel / AMDでたった3サイクルの待ち時間を持っているとすれば、これはほとんど問題になりません。小さな乗数は小さな入力に対してより多くの衝突を引き起こしますが、これは時々問題になるかもしれません。

素数は環Z /(2 ** 32)では意味がないため、素数を使用しても意味がありません。

だから、私はランダムに選ばれた大きな奇数を使うことを勧めます(素数を取ること自由に感じなさい)。 i86 / amd64 CPUは、1つの符号付きバイトに収まるオペランドに短い命令を使用できるため、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()が2つのオブジェクトに対してtrueを返す場合、hashCode()は同じ値を返すべきです。 equals()がfalseを返す場合、hashCode()は異なる値を返すべきです。


  • SquareCogのようにすでに気づいています。ハッシュコードが2つの文字列の連結から1回生成されると、衝突の塊を生成するのは非常に簡単です。("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")。ひどい傷です。両方のフィールドのハッシュコードを評価してから、それらの線形結合を計算する(できれば素数を係数として使用する)のが良いでしょう。 - Krzysztof Jabłoński
  • @KrzysztofJabłońskiそうです。また、交換fooそしてbar不必要な衝突も発生します。 - maaartinus

リンクされた質問


関連する質問

最近の質問