hashCode()がequals()と一貫性があることを確認する方法は?
質問
java.lang.Objectのequals()関数をオーバーライドするとき、javadocsは次のことを提案します
通常、hashCodeメソッドの一般規約を維持するために、このメソッドがオーバーライドされるたびにhashCodeメソッドをオーバーライドする必要があります。これは、等しいオブジェクトは等しいハッシュコードを持たなければならないことを示しています。
hashCode()メソッドは、各オブジェクトに対して固有の整数を返す必要があります(これは、メモリの場所に基づいてオブジェクトを比較する場合、固有の整数アドレスを返すだけです。オブジェクトの)
hashCode()メソッドをオーバーライドして、各オブジェクトのプロパティにのみ基づいて一意の整数を返すようにするにはどうすればよいですか?
public class People{
public String name;
public int age;
public int hashCode(){
// How to get a unique integer based on name and age?
}
}
/*******************************/
public class App{
public static void main( String args[] ){
People mike = new People();
People melissa = new People();
mike.name = "mike";
mike.age = 23;
melissa.name = "melissa";
melissa.age = 24;
System.out.println( mike.hasCode() ); // output?
System.out.println( melissa.hashCode(); // output?
}
}
解決
オブジェクトのハッシュコードは完全に一意である必要はなく、2つの等しいオブジェクトのハッシュコードが同じハッシュコードを返すというだけです。 2つの等しくないオブジェクトが同じハッシュコードを返すことは完全に合法です。ただし、ハッシュコードの分布がオブジェクトのセットに対して一意であるほど、HashMapsおよびhashCodeを使用するその他の操作から得られるパフォーマンスが向上します。
IntelliJ IdeaなどのIDEには、equalsおよびhashCodeの組み込みジェネレーターがあり、一般に<!> quot; good enough <!> quotの作成に非常に役立ちます。ほとんどのオブジェクトのコード(そしておそらくいくつかの手作りの非常に巧妙なハッシュ関数よりも優れています)。
たとえば、IdeaがPeopleクラスに対して生成するhashCode関数は次のとおりです。
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
他のヒント
hashCodeの一意性の詳細については、Marcが既に対処しているため、詳しく説明しません。 People
クラスの場合、最初に人の平等が何を意味するかを決定する必要があります。平等は名前だけに基づいているかもしれませんし、名前と年齢に基づいているかもしれません。ドメイン固有です。平等は名前と年齢に基づいているとしましょう。オーバーライドされたequals
は次のようになります
public boolean equals(Object obj) {
if (this==obj) return true;
if (obj==null) return false;
if (!(getClass().equals(obj.getClass())) return false;
Person other = (Person)obj;
return (name==null ? other.name==null : name.equals(other.name)) &&
age==other.age;
}
hashCode
をオーバーライドするときは常に、<=>をオーバーライドする必要があります。さらに、<=>は、<=>が使用したよりも多くのフィールドを計算で使用できません。ほとんどの場合、さまざまなフィールドのハッシュコードを追加または排他的に(またはハッシュコードを)追加する必要があります(hashCodeは計算が高速である必要があります)。したがって、有効な<=>メソッドは次のようになります。
public int hashCode() {
return (name==null ? 17 : name.hashCode()) ^ age;
}
以下は<=>が(高さ)しなかったフィールドを使用するため、無効であることに注意してください。この場合、2つの<!> quot; equals <!> quot;オブジェクトは異なるハッシュコードを持つことができます。
public int hashCode() {
return (name==null ? 17 : name.hashCode()) ^ age ^ height;
}
また、2つの等しくないオブジェクトが同じハッシュコードを持つことは完全に有効です:
public int hashCode() {
return age;
}
この場合、ジェーン30歳はボブ30歳と同じではありませんが、両方のハッシュコードは30です。これは有効ですが、ハッシュベースのコレクションのパフォーマンスには望ましくありません。
別の質問では、すべてのプログラマーが知っておくべき基本的な低レベルのものがあるかどうかを尋ねますが、ハッシュルックアップはそれらの1つだと思います。ここに行きます。
ハッシュテーブル(実際のクラス名を使用していないことに注意してください)は、基本的にリンクリストの配列です。テーブルで何かを見つけるには、まずその何かのハッシュコードを計算し、次にテーブルのサイズでそれを修正します。これは配列へのインデックスであり、そのインデックスでリンクリストを取得します。次に、オブジェクトが見つかるまでリストを走査します。
配列の取得はO(1)であり、リンクリストトラバーサルはO(n)であるため、オブジェクトが異なるリストにハッシュされるように、可能な限りランダムな分布を作成するハッシュ関数が必要です。すべてのオブジェクトはハッシュコードとして値0を返すことができ、ハッシュテーブルは引き続き機能しますが、基本的には配列の要素0の長いリンクリストになります。
また、通常は配列を大きくする必要があります。これにより、オブジェクトが長さ1のリストに含まれる可能性が高くなります。たとえば、Java HashMapは、マップのエントリ数が<!> gt;配列のサイズの75%。ここにはトレードオフがあります:エントリが非常に少なくメモリを浪費する巨大な配列、または配列内の各要素が<!> gt;のリストである小さな配列を使用できます。 1エントリ、および時間の浪費。完璧なハッシュは、各オブジェクトを配列内の一意の場所に割り当て、スペースを無駄にしません。
用語<!> quot;完全なハッシュ<!> quot;は実際の用語であり、場合によっては、各オブジェクトに一意の番号を提供するハッシュ関数を作成できます。これは、可能なすべての値のセットがわかっている場合にのみ可能です。一般的な場合、これを達成することはできず、同じハッシュコードを返す値がいくつかあります。これは単純な数学です。4バイトを超える文字列がある場合、一意の4バイトハッシュコードを作成できません。
1つの興味深いヒント:ハッシュ配列は一般に素数に基づいてサイズ設定され、ハッシュコードが実際にランダムであるかどうかに関係なく、結果を変更するときにランダム割り当ての最適な機会を与えます。
コメントに基づいて編集:
1)リンクリストは、同じハッシュコードを持つオブジェクトを表す唯一の方法ではありませんが、それはJDK 1.5 HashMapで使用される方法です。単純な配列よりもメモリ効率は低くなりますが、再ハッシュする際に間違いなく解約は少なくなります(エントリは1つのバケットからリンク解除され、別のバケットに再リンクできるため)。
2)JDK 1.4以降、HashMapクラスは2のべき乗としてサイズ設定された配列を使用します。それ以前は、N <!> lt; = 32の素数であると思う2 ^ N + 1を使用していました。これにより、配列のインデックス付け自体は高速化されませんが、ビットごとのANDで配列インデックスを計算できますニール・コフィーが指摘したように、部門よりも。個人的には、これを時期尚早の最適化として疑問に思うだろうが、HashMapの著者のリストを考えると、本当の利点があると思います。
一般に、可能なハッシュコード(整数)よりも多くの値があるため、ハッシュコードを一意にすることはできません。 適切なハッシュコードは、整数全体に値を分散します。 悪いものは常に同じ値を与えることができ、それでも論理的に正しい場合、受け入れられないほど非効率的なハッシュテーブルになります。
ハッシュテーブルが正しく機能するには、等しい値に同じハッシュ値が必要です。 それ以外の場合は、ハッシュテーブルにキーを追加し、別のハッシュコードを使用して等しい値を介してキーを検索してみてください。 または、異なるハッシュコードで等しい値を入力し、ハッシュテーブルの異なる場所に2つの等しい値を設定することもできます。
実際には、通常hashCode()メソッドとequals()メソッドの両方で考慮されるフィールドのサブセットを選択します。
あなたはそれを誤解したと思います。ハッシュコードは各オブジェクトに固有である必要はありません(結局、ハッシュコードです)が、すべてのオブジェクトで同一にしたくないのは明らかです。ただし、等しいすべてのオブジェクトと同一である必要があります。そうしないと、標準コレクションのようなものは機能しません(たとえば、ハッシュセットで何かを検索しても見つからない)。
単純な属性の場合、一部のIDEにはハッシュコード関数ビルダーがあります。
IDEを使用しない場合は、Apahce CommonsとクラスHashCodeBuilderの使用を検討してください
hashCodeの唯一の契約上の義務は、一貫性のあるであることです。 hashCode値の作成に使用されるフィールドは、equalsメソッドで使用されるフィールドと同じか、フィールドのサブセットである必要があります。これは、効率的ではありませんが、すべての値に対して0を返すことは有効であることを意味します。
hashCodeに一貫性があるかどうかは、単体テストで確認できます。 EqualityTestCase という抽象クラスを作成しました。これは、いくつかのhashCodeチェックを実行します。テストケースを拡張し、2つまたは3つのファクトリメソッドを実装するだけです。このテストは、hashCodeが効率的かどうかをテストするという非常に粗雑な仕事をします。
これは、ハッシュコードの方法に関してドキュメントに記載されている内容です
@ javadoc
起動されるたびに 同じオブジェクトが複数回 Javaアプリケーションの実行、 hashCodeメソッドは一貫して 同じ整数を返します 等しい比較で使用される情報 オブジェクト上で変更されます。この 整数は一貫している必要はありません アプリケーションの1回の実行から 同じの別の実行へ アプリケーション。
ビジネスキーの概念があり、同じタイプの個別のインスタンスの一意性を決定します。ターゲットドメインとは別個のエンティティ(車両システムの車両など)をモデル化する特定のタイプ(クラス)にはそれぞれ、1つ以上のクラスフィールドで表されるビジネスキーが必要です。メソッドequals()とhasCode()は、ビジネスキーを構成するフィールドを使用して実装する必要があります。これにより、両方の方法が互いに整合することが保証されます。