web-dev-qa-db-ja.com

intプロパティが2つしかないカスタムクラスのhashCodeは何ですか?

Javaでは、int座標でポイントを表すクラスがあります

public class Point {
    int x = -1;
    int y = -1;

    public Point (int xNew, int yNew) {
        x = xNew; y = yNew;
    }

    public boolean equals (Object o) {
        // no need for (o instanceof Point) by design
        return x == ((Point)o).x && y == ((Point)o).y;
    }
}

クラスPointのオブジェクトをHashMapのキーとして、およびHashSetの要素として使用しています。

hashCode関数の最適な候補は何ですか?左部分がxで右部分がyになるように、2倍にします。たとえば、x = 4, y = 12の場合、hashCode4.12を返します。しかし、実装によって、それは倍精度になることはできず、intだけになります。

これはオプションではありません:

public int hashCode() {
    // no need to check for exception parseInt since x and y are valid by design
    return Integer.parseInt(Integer.toString(x) + Integer.toString(y));
}

xyは長すぎるため、一緒に変換されないためです。

31
Sophie Sperner

hashCodeのタイプを変更したり、変更したりすることはできません。

私はちょうど次のようなもので行きます:

public int hashCode() {
    return x * 31 + y;
}

これは、(a、b)が(b、a)とは異なることを意味します(たとえば、追加やXOR処理とは異なります)。これは便利な場合がありますif実際には、「切り替えられた」値のキーが使用されることになります。

ではない一意-ただし、ハッシュコードは一意である必要はありません。それらはjustが等しい値(正確さのため)と同じである必要があり、(効率のために)等しくない値に対して「通常」異なっており、妥当な分布を持っている必要があります。

一般に、私は通常、Josh BlochがEffective Javaで提案しているのと同じ種類のパターンに従います。

public int hashCode() {
    int hash = 17;
    hash = hash * 31 + field1Hash;
    hash = hash * 31 + field2Hash;
    hash = hash * 31 + field3Hash;
    hash = hash * 31 + field4Hash;
    ...
    return hash;
}

どこ field1Hashは、参照型フィールドのハッシュコード(またはnull参照の場合は0)、int値の場合はint自体、longの場合は64ビットから32までのハッシュなどです。

編集:31と17がうまく機能する理由の詳細を思い出せません。両方が素数であるという事実mayは有用です-しかし、私が覚えていることから、このようなハッシュが一般的になぜであるかの背後にある数学は、一般的にreasonable(ただし、可能性の高い値の分布が事前にわかっている場合)、難しいか、よく理解されていません。私は31を掛けることが安いことを知っています(左に5シフトして元の値を差し引く)...

39
Jon Skeet

Java.util.Objects.hash(Object ... values) を使用するだけです。

public int hashCode() {
    return Objects.hash(field1,field2);
}

Objects.hashが実際に呼び出す Arrays.hashCode(Object a [])

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;

    int result = 1;

    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}
8
YieldNull

等しくないオブジェクトが同じハッシュコードを持つことは問題ありません。ただし、衝突が増えると、パフォーマンスが低下します(たとえば、ハッシュテーブルで)。

私の知る限り、[〜#〜] z [〜#〜]²→[〜#〜] z [〜#〜]からの最良のマッピングは、 「エレガントなペアリング機能」(グーグルit)。ここに実装があります

// x,y must be non-negative
int elegant(int x, int y) {
    return x < y ? y * y + x : x * x + x + y;
}


// returns a unique number for every x,y pair
int elegantSigned(int x, int y) {
    if (x < 0) {
        if (y < 0)
            return 3 + 4 * elegant(-x - 1, -y - 1);
        return 2 + 4 * elegant(-x - 1, y);
    }
    if (y < 0)
        return 1 + 4 * elegant(x, -y - 1);
    return 4 * elegant(x, y);
}

これは、乗算オーバーフローが発生するとすぐに重なり始めます。 xとyの絶対値が約46000未満の場合、これにはzeroハッシュ衝突があります。

8
John Henckel

多くの場合、検討する価値があります Apache Commons HashCodeBuilder

このクラスを使用すると、あらゆるクラスに対して適切なhashCodeメソッドを構築できます。これは、Joshua Bloch著の本「Effective Java」で説明されている規則に従います。優れたhashCodeメソッドを書くことは実際には非常に困難です。このクラスはプロセスを簡略化することを目的としています

参照されている本Effective Javaを見ることをお勧めします。

3
Brian Agnew

ハッシュコード操作を生成する一般的な戦略があります。あなたの場合これは次のようになります:

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + x;
    result = prime * result + y;
    return result;

}

2
Leonkur

Google Guava'sObjects.hashCode(Object ...) メソッドをご覧ください。

public int hashCode() {
  return Objects.hashCode(x, y);
}
1
t3chris

この質問はかなり古いですが、Javaが存在する限り、まさにそのアイデアが現実のものになると思います。上記のアプローチを分析してみましょう:

  1. Objects.hashCode(...)なめらかで何をする必要があるかを明確にする、しかしそれはvarargsを使用する(暗黙的に配列を作成する)そしてさらに暗黙的にボックスメソッドに渡されるすべてのプリミティブ。
  2. x * 31 + y is performance-efficient:ボクシングはなく、明示的または暗黙的な配列作成操作は使用されていません。しかし、それはnclear何をする必要があるかです。 42 ではなく31なのはなぜですか?ハッシュの仕組みに慣れている人にとっては、そのようなコードを理解するのは難しくありませんが、他の人にとってはどうでしょうか? 2つ目の落とし穴は拡張が難しいです。たとえば、3Dに移行して追加したい場合、ハッシュコードに新しい値を追加するのを忘れがちですzほぼ同じコードを何度もコピーして貼り付ける必要があるためです。

上記の回答では言及されていない3番目のアプローチを紹介します。

@Override
public final int hashCode()
{
    final int[] numbers = {x, y};
    return Arrays.hashCode(numbers);
}

ハッシュされる整数を保持するために一時配列を使用し、 Arrays.hashCode() を呼び出します。これはJava 1.5以降で使用できます。他のプリミティブ型のバージョンもあります。

Pros[〜#〜] dry [〜#〜] で、流暢で、何をする必要があるかを完全に明確にします。暗黙的なボクシングの影響を受けず、暗黙的な可変引数を使用しません。比較的高速で安価です。配列初期化子に数値を追加することで簡単に拡張できます。

短所:コピーして貼り付ける方法ほど高速ではありません。ハッシュコードが頻繁に呼び出される場合は考慮してください。

宜しくお願いします。

1
Netherwire

ハッシュコードを追加してみてください。 ?

新しいInteger(x).hashCode()+ new Integer(y).hashCode();を返します。

0