32

Eclipse のソース メニューには、次のような関数を生成する「hashCode の生成 / equals メソッド」があります。

String name; 
@Override
public int hashCode()
{
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

@Override
public boolean equals(Object obj)
{
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    CompanyRole other = (CompanyRole) obj;
    if (name == null)
    {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

生成時に複数のフィールドを選択するhashCode()と、equals()Eclipse は上記と同じパターンを使用します。

私はハッシュ関数の専門家ではありませんが、生成されたハッシュ関数がどれほど「優れている」か知りたいですか? 故障して衝突が多すぎるのはどのような状況ですか?

4

8 に答える 8

19

java.util.ArrayList次のように hashCode 関数の実装を確認できます。

public int hashCode() {
    int hashCode = 1;
    Iterator<E> i = iterator();
    while (i.hasNext()) {
        E obj = i.next();
        hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
    }
    return hashCode;
}

これはその一例であり、Eclipse で生成されたコードは、同様の実装方法に従います。ただし、hashCode を自分で実装する必要があると思われる場合は、Joshua Bloch が著書『Effective Java 』で示した優れたガイドラインがいくつかあります。それらの重要なポイントは、その本の項目 9 から掲載します。それらは、

  1. result という名前の int 変数に、0 以外の一定の値、たとえば 17 を格納します。
  2. オブジェクトの重要なフィールド f (equals メソッドによって考慮される各フィールド) ごとに、次の操作を行います。

    を。フィールドの int ハッシュ コード c を計算します。

    私。フィールドがブール値の場合、(f ? 1 : 0) を計算します。

    ii. フィールドが byte、char、short、または int の場合、(int) f を計算します。

    iii. フィールドが long の場合、(int) (f ^ (f >>> 32)) を計算します。

    iv。フィールドが float の場合は、Float.floatToIntBits(f) を計算します。

    v. フィールドが double の場合、Double.doubleToLongBits(f) を計算し、結果の long をステップ 2.a.iii のようにハッシュします。

    vi. フィールドがオブジェクト参照であり、このクラスの equals メソッドが equals を再帰的に呼び出してフィールドを比較する場合、フィールドで hashCode を再帰的に呼び出します。より複雑な比較が必要な場合は、このフィールドの「正規表現」を計算し、正規表現で hashCode を呼び出します。フィールドの値が null の場合は、0 を返します (またはその他の定数ですが、0 が伝統的です)。

    vii. フィールドが配列の場合は、各要素が個別のフィールドであるかのように扱います。つまり、これらのルールを再帰的に適用して重要な要素ごとにハッシュ コードを計算し、ステップ 2.b に従ってこれらの値を結合します。配列フィールドのすべての要素が重要な場合、リリース 1.5 で追加された Arrays.hashCode メソッドのいずれかを使用できます。

    b. 次のように、手順 2.a で計算されたハッシュ コード c を結果に結合します。

       result = 31 * result + c;
    
  3. 結果を返します。

  4. hashCode メソッドの記述が完了したら、等しいインスタンスのハッシュ コードが等しいかどうかを自問してください。単体テストを書いて直感を検証しましょう! 等しいインスタンスのハッシュ コードが等しくない場合は、その理由を突き止めて問題を解決してください。

Java 言語設計者と Eclipse は、私が推測する同様のガイドラインに従っているようです。ハッピーコーディング。乾杯。

于 2012-08-03T12:05:31.137 に答える
14

java.util.ObjectsJava 7以降、短くてエレガントなメソッドを書くために使用できます:

class Foo {
  private String name;
  private String id;

  @Override
  public int hashCode() {
    return Objects.hash(name,id);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Foo) {
      Foo right = (Foo) obj;
      return Objects.equals(name,right.name) && Objects.equals(id,right.id);
    }
    return false;
  }
}
于 2015-10-14T08:23:54.843 に答える
5

一般的には良いですが:

  1. グアバはそれを何とかうまくやっています、私はそれを好みます。[編集: JDK7 の時点で、Java は同様のハッシュ関数を提供しているようです]。
  2. Hibernateなどの一部のフレームワークでは、setter/getter を使用する代わりにフィールドに直接アクセスすると問題が発生する可能性があります。Hibernate が遅延作成する一部のフィールドでは、実際のオブジェクトではなくプロキシを作成します。getter を呼び出すだけで、Hibernate はデータベース内の実際の値を取得できます。
于 2012-08-03T11:59:10.227 に答える
4

はい、それは完璧です:)このアプローチはJavaソースコードのほぼすべての場所で見られます。

于 2012-08-03T11:47:37.880 に答える
0

また、Joshua Bloch による『Effective Java 2nd Edition』の項目 9 への参照を追加したいと思います。

からのレシピはこちらItem 9 : ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS

  1. result という名前の int 変数に、0 以外の一定の値、たとえば 17 を格納します。
  2. オブジェクトの重要なフィールド f (equals メソッドによって考慮される各フィールド) ごとに、次の操作を行います。
    a. Compute an int hash code c for the field:            
        i.   If the field is a boolean, compute (f ? 1 : 0).
        ii.  If the field is a byte, char, short, or int, compute (int) f.
        iii. If the field is a long,compute(int)(f^(f>>>32)).
        iv.  If the field is a float, compute Float.floatToIntBits(f).
        v.   If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii.
        vi.  If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
        vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5. 
   b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c;
3. Return result.
4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem.
于 2012-08-03T20:23:46.860 に答える
0

これは、ハッシュ関数を記述する標準的な方法です。ただし、フィールドに関する知識があれば、改善/簡素化できます。たとえば、クラスがフィールドが決して null にならないことを保証している場合 (equals() にも適用されます)、null チェックを省略できます。または、フィールドが 1 つしか使用されていない場合は、フィールドのハッシュ コードを委任できます。

于 2012-08-03T12:39:58.970 に答える
0

潜在的な欠点の 1 つは、null フィールドを持つすべてのオブジェクトのハッシュ コードが 31 になることです。そのため、null フィールドのみを含むオブジェクト間で多くの衝突が発生する可能性があります。これにより、 での検索が遅くなりますMaps

Mapこれは、キー タイプに複数のサブクラスがある がある場合に発生する可能性があります。たとえば、 がある場合HashMap<Object, Object>、ハッシュ コードが 31 のキー値を多数持つことができます。確かに、これはそれほど頻繁には発生しません。必要に応じて、素数の値を 31 以外の値にランダムに変更して、衝突の可能性を減らすことができます。

于 2018-08-20T12:14:39.987 に答える