hashCode()
コレクションのメソッドの最適な実装をどのように決定しますか(equalsメソッドが正しくオーバーライドされていると想定)。
collection.hashCode()
(hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/...)
hashCode()
コレクションのメソッドの最適な実装をどのように決定しますか(equalsメソッドが正しくオーバーライドされていると想定)。
collection.hashCode()
(hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/...)
回答:
最高の実装?これは使用パターンに依存するため、難しい質問です。
ほぼすべての場合に適した適切な実装が、項目8(第2版)のJosh Blochの Effective Javaで提案されました。著者がそこにアプローチがなぜ良いのかを説明しているので、そこを調べるのが一番です。
を作成し、ゼロ以外の値int result
を割り当てます。
以下のためにあらゆる分野 f
でテストequals()
方法、ハッシュコードを計算するc
ことで:
boolean
:計算し(f ? 0 : 1)
ます。byte
、char
、short
またはint
:計算(int)f
;long
:計算し(int)(f ^ (f >>> 32))
ます。float
:計算しFloat.floatToIntBits(f)
ます。double
:Double.doubleToLongBits(f)
すべてのlong値のように戻り値を計算して処理します。hashCode()
メソッドの結果を使用しf == null
ます。ハッシュ値を以下c
と組み合わせますresult
:
result = 37 * result + c
戻る result
これにより、ほとんどの使用状況でハッシュ値が適切に分散されます。
dmeisterが推奨する効果的な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
)が必要ですが、同じように動作します。
hashCode
は、カスタムがある場合equals
であり、それがまさにこれらのライブラリメソッドの目的です。ドキュメントは、との関連での動作が非常に明確equals
です。ライブラリの実装は、正しいhashCode
実装の特性を知ることからあなたを免除することを主張するものではありません。これらのライブラリにより、がオーバーライドされるほとんどの場合に、このような適合実装を簡単に実装できequals
ます。
java.util.Objects.hash(...)
、グアバcom.google.common.base.Objects.hashCode(...)
方式ではなくJDK7 方式を選択した方がいいですが、ベストアンサーのIMO 。ほとんどの人は、追加の依存関係よりも標準ライブラリを選択すると思います。
hashCode()
、配列は単なるであるため、結果は期待どおりにならない可能性がありますjava.lang.System.identityHashCode(...)
。
Eclipseが提供する機能を使用することをお勧めします。これはかなり良い仕事をしており、ビジネスロジックの開発に努力とエネルギーを注ぐことができます。
これはAndroid
ドキュメント(ウェイバックマシン)とGithub上の私の独自のコードにリンクされていますが、一般的にJavaで動作します。私の答えは、dmeisterの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;
}
編集
通常、をオーバーライドすると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));
}
最初に、equalsが正しく実装されていることを確認してください。IBM developerWorksの記事:
- 対称性:2つの参照aとbの場合、b.equals(a)の場合のみ、a.equals(b)
- 反射性:すべてのnull以外の参照の場合、a.equals(a)
- 推移性:a.equals(b)およびb.equals(c)の場合、a.equals(c)
次に、hashCodeとの関係が連絡先を尊重することを確認します(同じ記事から):
- hashCode()との整合性:2つの等しいオブジェクトは同じhashCode()値を持つ必要があります
最後に、優れたハッシュ関数は、理想的なハッシュ関数に近づくよう努めるべきです。
about8.blogspot.com、あなたは言った
equals()が2つのオブジェクトに対してtrueを返す場合、hashCode()は同じ値を返します。equals()がfalseを返す場合、hashCode()は異なる値を返す必要があります
私はあなたに同意できません。2つのオブジェクトが同じハッシュコードを持っている場合、それらが等しいことを意味する必要はありません。
AがBと等しい場合、A.hashcodeはB.hascodeと等しくなければなりません。
だが
A.hashcodeがB.hascodeと等しい場合、AがBと等しくなければならないという意味ではありません
(A != B) and (A.hashcode() == B.hashcode())
、それは私たちがハッシュ関数衝突と呼んでいるものです。これは、ハッシュ関数のコドメインが常に有限であるのに対し、ドメインは通常有限ではないためです。コドメインが大きいほど、衝突の発生頻度は低くなります。優れたハッシュ関数は、特定のコドメインサイズが与えられた場合に達成できる可能性が最も高い、異なるオブジェクトに対して異なるハッシュを返す必要があります。ただし、完全に保証されることはほとんどありません。
Eclipseを使用する場合、以下を生成equals()
してhashCode()
使用できます。
ソース-> hashCode()およびequals()を生成します。
この関数を使用すると、等価性とハッシュコードの計算に使用するフィールドを決定でき、Eclipseは対応するメソッドを生成します。
良いの実装があります効果的なJavaののhashcode()
とequals()
では、ロジックのApache Commonsのラングは。HashCodeBuilderとEqualsBuilderをチェックアウトします。
Objects
クラスはJava7以降のhash(Object ..args)
&equals()
メソッドを提供します。これらは、jdk 1.7+を使用するすべてのアプリケーションに推奨されます
IdentityHashMap
。FWIW私はすべてのエンティティにidベースのhashCodeおよびequalsを使用しています。
(コードの観点から)他のより詳細な回答を完了するための簡単なメモ:
質問how-do-i-create-a-hash-table-in-java、特にjGuru FAQエントリを検討すると、ハッシュコードを判断できる他の基準がいくつかあります。
私があなたの質問を正しく理解している場合、カスタムコレクションクラス(つまり、Collectionインターフェイスから拡張された新しいクラス)があり、hashCode()メソッドを実装したいと考えています。
コレクションクラスがAbstractListを拡張する場合は、心配する必要はありません。equals()およびhashCode()の実装がすでにあり、すべてのオブジェクトを反復処理して、それらのhashCodes()を追加することで機能します。
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メソッドで使用するすべてのフィールドを処理します。
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
@ 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をスキップしてください。醜いです。)
foo
とbar
同じにつながりhashCode
ます。あなたのtoString
私の知る限りではコンパイルされませんし、それがない場合、それは非効率的なひどいです。のようなもの109 * getFoo().hashCode() + 57 * getBar().hashCode()
はより速く、よりシンプルで、不必要な衝突を引き起こしません。
Apache Commons EqualsBuilderおよびHashCodeBuilderでリフレクションメソッドを使用します。
Arrays.deepHashCode(...)
パラメータとして提供された配列を正しく処理するため、小さなラッパーを使用しています
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
可能な範囲にハッシュ値を均等に分散するハッシュ方式は、適切な実装です。効果的なJavaを参照(http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_resultres there &tipresult&result&result&result&result&ハッシュコード実装のためにそこにあります(アイテム9と思います...)。
これは、スーパークラスロジックを考慮した別の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());
}
}
標準実装は脆弱であり、それを使用すると不要な衝突が発生します。想像してみて
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))
同じhashCode
、つまり31*(a+b) + c
、使用される乗数と同じList.hashCode
がここで再利用されます。衝突は避けられないことは明らかですが、不必要な衝突が発生するのは...不必要です。
を使用することについて、実質的に賢いことは何もありません31
。情報の損失を防ぐために、乗数は奇数でなければなりません(偶数乗数は少なくとも最上位ビットを失い、4の倍数は2を失うなど)。任意の奇数乗数が使用可能です。乗算器が小さいと計算が高速になる可能性があります(JITはシフトと加算を使用できます)が、乗算が最新のIntel / AMDで3サイクルのレイテンシしかないことを考えると、これはほとんど問題になりません。小さな乗数はまた、小さな入力に対してより多くの衝突を引き起こし、時々問題になるかもしれません。
素数は環Z /(2 ** 32)では意味がないため、素数を使用しても意味がありません。
ですから、ランダムに選ばれた大きな奇数を使用することをお勧めします(素数を自由に使ってください)。i86 / amd64 CPUは、単一の符号付きバイトに適合するオペランドに短い命令を使用できるため、109のような乗数には速度上の小さな利点があります。衝突を最小限に抑えるには、0x58a54cf5のようなものを使用します。
さまざまな場所でさまざまな乗数を使用することは役立ちますが、追加の作業を正当化するのにおそらく十分ではありません。
ハッシュ値を組み合わせるとき、私は通常、boost c ++ライブラリで使用されている結合方法を使用します。
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
これは、均等な配分を確保するのにかなり良い仕事をします。この数式がどのように機能するかの説明については、StackOverflowの投稿を参照してください。 boost :: hash_combineのマジックナンバー
さまざまなハッシュ関数については、http://burtleburtle.net/bob/hash/doobs.htmlでよく議論されています。
単純なクラスの場合、equals()実装によってチェックされるクラスフィールドに基づいてhashCode()を実装することが最も簡単です。
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
最も重要なことは、hashCode()とequals()の一貫性を保つことです。equals()が2つのオブジェクトに対してtrueを返す場合、hashCode()は同じ値を返す必要があります。equals()がfalseを返す場合、hashCode()は異なる値を返す必要があります。
("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")
。ひどい傷です。両方のフィールドのハッシュコードを評価してから、それらの線形結合を計算することをお勧めします(できれば素数を係数として使用します)。
foo
とbar
あまりにも、不必要な衝突を生成します。
Objects.hashCode(collection)
、完璧な解決策になるはずです。