どのように我々はの最良の実装を決めるのですかhashCode()
コレクションのメソッド(equalsメソッドが正しくオーバーライドされたと仮定して)
最高の実装は?それは使用パターンに依存するのでそれは難しい質問です。
ほぼすべての場合において、合理的で適切な実装が提案されています。ジョシュブロッホの効果的なJavaアイテム8(第2版)に。作者がそこでアプローチが良い理由を説明しているので、それをそこで調べることが最善です。
を作成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()
methodまたは次の場合は0f == null
;ハッシュ値を組み合わせるc
とresult
:
result = 37 * result + c
戻るresult
これにより、ほとんどの使用状況でハッシュ値が正しく分散されるはずです。
dmeisterによって推奨されているEffective Javaの実装に満足しているなら、独自のものを使う代わりにライブラリ呼び出しを使うことができます。
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
これにはグアバが必要です(com.google.common.base.Objects.hashCode
)またはJava 7の標準ライブラリ(java.util.Objects.hash
)同じように動作します。
hashCode
習慣があるならばequals
それがまさにこれらのライブラリメソッドが設計されているものです。ドキュメンテーションはに関連してそれらのふるまいについてはかなりはっきりしていますequals
。ライブラリの実装は、正しいとはどのような特徴があるのかを知ることからあなたを免除するとは主張しません。hashCode
実装は - これらのライブラリはそれを作るより簡単にほとんどの場合、そのような適合実装を実装する必要があります。equals
オーバーライドされます。 - bacarjava.util.Objects.hash(...)
グアバよりも方法com.google.common.base.Objects.hashCode(...)
方法。私はほとんどの人が特別な依存関係よりも標準ライブラリを選ぶだろうと思います。 - Malte SkoruppahashCode()
配列のためだけですjava.lang.System.identityHashCode(...)
。 - starikoff
Eclipseで提供されている機能を使用したほうがよいでしょう。これはかなり良い仕事をし、ビジネスロジックの開発にあなたの努力とエネルギーを注ぐことができます。
これはにリンクされていますがAndroid
ドキュメンテーション(Wayback Machine)そしてGithubの私自身のコード、それは一般的にJavaのために働くでしょう。私の答えはdmeister's 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の記事:
- 対称性:aとbの2つの参照の場合、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())
これがハッシュ関数の衝突と呼ばれるものです。これは、ハッシュ関数のコドメインが常に有限であるのに対し、そのドメインは通常有限ではないためです。コドメインが大きいほど、衝突が発生する頻度は少なくなります。良いハッシュ関数は、特定のコドメインサイズを考えれば達成可能な最大の可能性で、異なるオブジェクトに対して異なるハッシュを返すべきです。それが完全に保証されることはめったにありません。 - Krzysztof Jabłoński
あなたが日食を使用する場合は、あなたが生成することができますequals()
そしてhashCode()
を使用して:
ソース - > hashCode()とequals()を生成します。
この機能を使用してあなたが決めることができますどの分野等価性とハッシュコードの計算に使いたい場合は、Eclipseが対応するメソッドを生成します。
の良い実装があります効果的なJavaのhashcode()
そしてequals()
ロジックインApache Commons Lang。チェックアウトHashCodeBuilderそしてEqualsBuilder。
Objects
クラスが提供するhash(Object ..args)
&equals()
Java7以降のメソッドこれらはjdk 1.7+を使用するすべてのアプリケーションに推奨されます - DiabloIdentityHashMap
)私はidベースのhashCodeを使用しており、すべてのエンティティに等しいです。 - maaartinus
(コードに関して)他のより詳細な答えを完成させるためのちょっとしたメモ:
私が質問を考えるならHow-do-i-Javaでハッシュテーブルを作成するそして特にjGuru FAQエントリーハッシュコードを判断できる他の基準は、次のとおりです。
私があなたの質問を正しく理解したならば、あなたはカスタムコレクションクラス(すなわち、Collectionインターフェースから拡張する新しいクラス)を持っていて、そしてあなたはhashCode()メソッドを実行したいです。
コレクションクラスがAbstractListを拡張する場合は、心配する必要はありません。すべてのオブジェクトを繰り返し処理し、それらのhashCode()を一緒に追加することによって機能するequals()とhashCode()の実装がすでにあります。
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メソッドで使用するすべてのフィールドを処理するために通常^(bitwise exclusive or)演算子を使用します。
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をスキップしてください。見苦しいです。)
あなたが特にコレクションを要求したとき、私は他の答えがまだ述べていないという側面を加えたいと思います:HashMapは彼らがコレクションに加えられたら彼らのキーが彼らのハッシュコードを変えることを期待しません。全体の目的を打ち負かすだろう...
Apache Commonsでリフレクションメソッドを使うEqualsBuilderそしてHashCodeBuilder。
ハッシュ値を可能な範囲に均等に分配するハッシング方法は良い実装です。有効な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_result&resnum=1&ct=result)、ハッシュコード実装のためのそこに良いヒントがあります(項目9私は思う...)。
ユーティリティメソッドfrommを使うのが好きです。Google Collections libクラスのObjectsからそれは私が私のコードをきれいに保つのを助けます。よくequals
そしてhashcode
メソッドはIDEのテンプレートから作られているので、読みやすくはありません。
私は周りに小さなラッパーを使用していますArrays.deepHashCode(...)
パラメータとして提供された配列を正しく処理するため
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
これはスーパークラスロジックを考慮したJDK 1.7以降のアプローチのもう1つのデモンストレーションです。 ObjectクラスのhashCode()が考慮されていること、純粋なJDK依存関係があること、および余分な手作業がないことに、私はかなり納得していると思います。ご注意くださいObjects.hash()
ヌルトレラントです。
含まれていません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は、1つの符号付きバイトに収まるオペランドに短い命令を使用できるため、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")
。ひどい傷です。両方のフィールドのハッシュコードを評価してから、それらの線形結合を計算する(できれば素数を係数として使用する)のが良いでしょう。 - Krzysztof Jabłońskifoo
そしてbar
不必要な衝突も発生します。 - maaartinus
Objects.hashCode(collection)
完璧な解決策でなければなりません! - Diablocollection.hashCode()
(hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/…) - cbreezier