HashSet
を使用するコードをデバッグしようとしていて、SO全体を検索していたところ、hashCode
メソッドもオーバーライドする必要があることがわかりました。奇妙な部分は、 関連するAPI をチェックすることですが、hashCode
メソッドについて言及している部分はありませんでした。 APIで見られるadd
のHashSet
メソッドの定義を引用します。
公開ブールadd(E e)
指定された要素がまだ存在しない場合は、このセットに追加します。より正式には、(e == null?e2 == null:e.equals(e2))のような要素e2がこのセットに含まれていない場合、指定された要素eをこのセットに追加します。このセットにすでに要素が含まれている場合、呼び出しはセットを変更せずにfalseを返します。
さて、上記の引用では、hashCode
メソッドについて言及しているところはどこにもありません。正しいステートメントは次のようになっているべきではありません:
...このセットに(e == null?e2 == null:e.equals(e2))のような要素e2が含まれていない場合ANDこのセットに(e == null?e2 == nullのような要素e2が含まれていない場合:e.hashCode()== e2.hashCode())。
ここで、「o1.equals(o2)
がtrueを返す場合、o1.hashCode()
== o2.hashCode()
もtrueと評価する必要があります。」という場合、3つの質問をします。
その事実はどこに明記されていますか? (一般的に、またはAPI内)
その事実がどこかで指定されている場合でも、APIのどこでHashSet
がhashCode
メソッドを使用するように指定されていますか?
その事実が本当に正しい場合、hashCode
メソッドがオーバーライドされるときはいつでも、コンパイラーはequals
メソッドのオーバーライドを強制しないのはなぜですか?
オブジェクトのハッシュコード値を返します。このメソッドは、HashMapによって提供されるようなハッシュテーブルの利点のためにサポートされています。
HashCodeの一般的な規約は次のとおりです。
- Javaアプリケーションの実行中に同じオブジェクトで複数回呼び出される場合は常に、オブジェクトの等値比較で使用される情報が変更されない限り、hashCodeメソッドは常に同じ整数を返す必要があります。この整数は、あるアプリケーションの実行から同じアプリケーションの別の実行まで一貫性を保つ必要はありません。
- equals(Object)メソッドに従って2つのオブジェクトが等しい場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、同じ整数の結果が生成される必要があります。
- Equals(Java.lang.Object)メソッドに従って2つのオブジェクトが等しくない場合、2つのオブジェクトのそれぞれでhashCodeメソッドを呼び出すと、異なる整数の結果が生成される必要はありません。ただし、プログラマは、異なるオブジェクトに対して異なる整数の結果を生成すると、ハッシュテーブルのパフォーマンスが向上する可能性があることに注意する必要があります。
APIでのhashCodeの詳細は、HashSetから数ステップ深くなります。
Set
インスタンス)に裏打ちされたHashMap
インターフェースを実装しています。」)そこで、それは読みます:
オブジェクトのハッシュコード値を返します。このメソッドは、
HashMap
によって提供されるハッシュテーブルなどの利点のためにサポートされています。
コンパイラーは、オブジェクトまたはメソッド間の関係を知りませんし、気にしません。一方、doが気にする静的分析ツールがいくつかあります。
Findbugs クラスはequals()を定義しますが、hashCode()は定義しません :
このクラスは
equals(Object)
をオーバーライドしますが、hashCode()
はオーバーライドしません。したがって、クラスは、等しいオブジェクトは等しいハッシュコードを持つ必要があるという不変条件に違反する可能性があります。
Findbugs クラスはequals()を定義し、Object.hashCode()を使用します
このクラスは
equals(Object)
をオーバーライドしますが、hashCode()
をオーバーライドせず、hashCode()
の実装を_Java.lang.Object
_(アイデンティティハッシュコードを返す)から継承します。 VMによってオブジェクトに割り当てられた任意の値)。したがって、クラスは、等しいオブジェクトが等しいハッシュコードを持たなければならないという不変条件に違反する可能性が非常に高くなります。このクラスのインスタンスがHashMap/HashTableに挿入されると思わない場合は、使用することをお勧めするhashCode実装は次のとおりです。
_public int hashCode() { assert false : "hashCode not designed"; return 42; // any arbitrary constant will do }
_
PMD OverrideBothEqualsAndHashcode
Public boolean Object.equals(Object other)とpublic int Object.hashCode()の両方をオーバーライドするか、どちらもオーバーライドしません。親クラスからhashCode()を継承している場合でも、hashCodeの実装とスーパークラスへの明示的な委任を検討してください。
CheckStyle EqualsHashCode
equals()
をオーバーライドするクラスがhashCode()
もオーバーライドすることを確認します。根拠:
equals()
およびhashCode()
の規約では、等しいオブジェクトが同じhashCodeを持つ必要があります。したがって、equals()
をオーバーライドする場合は常に、hashCode()
をオーバーライドして、クラスがハッシュベースのコレクションで使用できるようにする必要があります。
一部のIDEには、等しいステップとハッシュコードを作成するための静的分析ツールまたはジェネレーターが組み込まれている場合があります-場合によっては同じステップの一部として(これらは対象のフィールドです-poofコードがあります)。
...このセットに(e == null?e2 == null:e.equals(e2))のような要素e2が含まれていない場合ANDこのセットに(e == null?e2 == nullのような要素e2が含まれていない場合:e.hashCode()== e2.hashCode())。
上記の記述は正しくありません。一般に、オブジェクトe1をHashSetまたはSetに追加できます。この場合、セットにはe1.hashCode() == e2.hashCode() && e1.equals(e2) == false
を満たす要素e2が含まれます。
これの例は簡単に作成できます。名前、姓、および居住都市の属性を持つクラスPersonを想像してください。 equalsメソッドはすべての属性を比較し、hashCodeメソッドは居住都市のハッシュコードを使用します。 equalsとhashCodeの契約は履行されますが、上記のaddの契約では、同じ都市に住んでいるセットに人を追加することはできません。
HashSetのドキュメントには、メソッドhashCodeの使用が明示的に記載されていません(実装の詳細を検討します。知っておく必要がある重要なことは、HashSetがSetの規約を満たしていることです)。ただし、 ドキュメント にヒントがあります。
このクラスは、ハッシュ関数が要素をバケット間で適切に分散すると仮定して、基本操作(追加、削除、包含、サイズ)に対して一定の時間パフォーマンスを提供します。
- その事実はどこに明記されていますか? (一般的に、またはAPI内)
.equals()
メソッドの documentation で明確に指定されています。
等しいオブジェクトは等しいハッシュコードを持たなければならないことを示すhashCodeメソッドの一般規約を維持するために、このメソッドがオーバーライドされるときは常にhashCodeメソッドをオーバーライドする必要があることに注意してください。
.equals()
メソッドをオーバーライドし、オーバーライドしたメソッドのドキュメントを参照せずに、その要件に違反しました。
- その事実がどこかで指定されていても、APIのどこでHashSetがhashCodeメソッドを使用するように指定されているのでしょうか。
どこにも指定する必要はありません。 .hashCode()
はObject
のメソッドであるため、すべてのオブジェクトにそれがあります。 Anyクラスはそれを利用できます。
HashSet
が使用する.hashCode()
は、APIの一部ではなく、実装の詳細です。 HashSet
のAPIは、基本的に、それが実装するSet
インターフェースのAPIと同じです。 HashSet
は、Set
が追加する以上の、タイプに関する追加の要件を追加しません。 Set
コントラクトは、セットに2つの.equals()
要素がないことを確認し、.equals()
を使用して検索メソッドを検索します。 HashSet
も同じです。 HashSet
は、これらの操作の一部として.hashCode()
を使用しますが、.hashCode()
は一部として.equals()
と一貫しているはずなので、安全に実行できるはずです.equals()
の契約の。
- その事実が実際に正しい場合、equalsメソッドがオーバーライドされると、コンパイラーはhashCodeメソッドのオーバーライドを強制しないのはなぜですか?
これを強制するための言語のメカニズムはありません。
そうしないことの明示的な約束がない場合、-any等号ベースのコレクションは、一致しないとすぐに判断する傾向がある場合、hashCode
を使用できます(関係するソートされたコレクションに注意)一致するように同じランクのアイテムはそうすることはできません。さらに、ハッシュセットの実装が常にhashCodeを呼び出すという保証はありません。 hashSet
の実装があり、いくつかのアイテムが含まれるようになるまで、何もhashCode
を呼び出さなくても問題はありません。これは、非常に多くのアイテムが追加されることのない多くのコレクションインスタンスを生成する一部のネストされたコレクションコンテキストで役立ちます。したがって、hashSet
という名前は基本的にパフォーマンスがハッシュ関数の品質に結びつくことを示していますが、それがそれを使用できる、または使用する唯一のものであることを意味するわけではありません。