web-dev-qa-db-ja.com

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

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

283
Omnipotent

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

ほぼすべての場合において、合理的で適切な実装がItem 8(第2版)のJosh Bloch's Effective Javaで提案されました。作者がそこでアプローチが良い理由を説明しているので、それをそこで調べることが最善です。

ショートバージョン

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

  2. equals()メソッドでテストされたevery fieldfの場合、ハッシュコードcを次のように計算します。

    • フィールドfがbooleanの場合:(f ? 0 : 1)を計算します。
    • フィールドfがbytecharshortまたはintの場合:(int)fを計算します。
    • フィールドfがlongの場合:(int)(f ^ (f >>> 32))を計算します。
    • フィールドfがfloatの場合:Float.floatToIntBits(f)を計算します。
    • フィールドfがdoubleの場合:Double.doubleToLongBits(f)を計算し、すべてのlong値と同様に戻り値を処理します。
    • フィールドfがobjectの場合:hashCode()メソッドの結果を使用するか、f == nullの場合は0を使用します。
    • フィールドfがarrayの場合:すべてのフィールドを別々の要素として見て、ハッシュ値を再帰的な方法で計算し、次に説明するように値を組み合わせます。
  3. ハッシュ値cresultと結合します。

    result = 37 * result + c
    
  4. resultを返す

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

421
dmeister

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

132
bacar

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

58
Warrior

これは Android documentation(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;

}

EDIT

通常、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));
}
54

まずequalsが正しく実装されていることを確認してください。 From 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()値を持たなければなりません

最後に、良いハッシュ関数は 理想的なハッシュ関数 に近づくように努力するべきです。

17
Grey Panther

about8.blogspot.com、あなたは言った

equals()が2つのオブジェクトに対してtrueを返す場合、hashCode()は同じ値を返すべきです。 equals()がfalseを返す場合、hashCode()は異なる値を返すべきです

あなたに賛成できません。 2つのオブジェクトが同じハッシュコードを持っているなら、それはそれらが等しいということを意味する必要はありません。

AがBと等しい場合、A.hashcodeはB.hascodeと等しくなければなりません。

しかし

a.hashcodeがB.hascodeに等しい場合、AがBに等しいという意味ではありません。

11
panzupa

Apache Commons LangEffective Javahashcode()equals()ロジックの良い実装があります。チェックアウト HashCodeBuilder および EqualsBuilder

7
Rudi Adianto

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

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

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

7

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

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

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

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

コレクションクラスがAbstractListを継承する場合は、心配する必要はありません。equals()とhashCode()の実装は、すべてのオブジェクトを繰り返し処理してそれらの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);
}
4
Mario Ortegón

@ 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
SquareCog

あなたが具体的にコレクションを要求したとき、私は他の答えがまだ述べていないという側面を加えたいと思います。全体の目的を打ち負かすだろう...

2
Olaf Kock

Apache Commons EqualsBuilder および HashCodeBuilder でリフレクションメソッドを使用してください。

2
Vihung

標準的な実装は弱く、それを使うことは不必要な衝突を引き起こします。想像する

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

List.hashCodeに使用される乗数がここで再利用されるのと同じhashCode、すなわち31*(a+b) + cを持ちます。明らかに、衝突は避けられませんが、不必要な衝突を起こすのは単なる...不必要です。

31を使うことについて実質的に賢いことは何もありません。情報が失われないようにするため、乗数は奇数でなければなりません(偶数乗数であれば少なくとも最上位ビットが失われ、4の倍数で2が失われるなど)。任意の奇数乗数が使用可能です。小さい乗数はより速い計算につながるかもしれません(JITはシフトと加算を使うことができます)が、乗算が現代のIntel/AMDでたった3サイクルの待ち時間を持っているとすれば、これはほとんど問題ではありません。小さな乗数は小さな入力に対してより多くの衝突を引き起こしますが、これは時々問題になるかもしれません。

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

だから、私はランダムに選ばれた大きな奇数(素数を取ること自由に感じなさい)を使うことを勧めます。 i86/AMD64 CPUは、1つの符号付きバイトに収まるオペランドに短い命令を使用できるため、109のような乗算器では速度が非常に遅くなります。衝突を最小限に抑えるには、0x58a54cf5のようにします。

さまざまな場所でさまざまな乗数を使用することは有用ですが、おそらく追加の作業を正当化するのに十分ではありません。

1
maaartinus

ハッシュ値を可能な範囲に均等に分散させるハッシュ方法は、優れた実装です。有効なJavaを参照してください( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+Java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxresl========================をご覧ください。 = result )、ハッシュコード実装のための良いヒントがそこにあります(項目9私は思う...)。

1
Chii

これはスーパークラスロジックを使った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

MクラスObjectsのGoogle Collections libからユーティリティメソッドを使用することをお勧めします。これにより、コードをきれいに保つことができます。 equalsおよびhashcodeメソッドはIDEのテンプレートから作成されることが非常に多いので、それらを読むのはきれいではありません。

1
panzupa

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

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

ハッシュ値を組み合わせるときは、通常、boost c ++ライブラリで使われている組み合わせ方法を使う。

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

これは、均等な配布を確実にするというかなり良い仕事をします。この式がどのように機能するかについては、StackOverflowの投稿を参照してください。 boost :: hash_combineのマジックナンバー

さまざまなハッシュ関数の良い議論があります: http://burtleburtle.net/bob/hash/doobs.html

0
Edward Loper