コレクションに対するhashCode()
メソッドの最良の実装をどのように決定しますか(equalsメソッドが正しくオーバーライドされていると仮定して)。
最良の実装は?それは使用パターンに依存するのでそれは難しい質問です。
ほぼすべての場合において、合理的で適切な実装がItem 8(第2版)のJosh Bloch's Effective Javaで提案されました。作者がそこでアプローチが良い理由を説明しているので、それをそこで調べることが最善です。
int result
を作成し、ゼロ以外の値を割り当てます。
equals()
メソッドでテストされたevery fieldf
の場合、ハッシュコード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
の場合は0を使用します。ハッシュ値c
をresult
と結合します。
result = 37 * result + c
result
を返す
これにより、ほとんどの使用状況でハッシュ値が正しく分散されるはずです。
Dmeisterによって推奨されているEffective 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
)が必要ですが、同じように機能します。
Eclipseで提供されている機能を使用したほうがよいでしょう。これはかなり良い仕事をし、ビジネスロジックの開発にあなたの努力と力を注ぐことができます。
これは Android
documentation(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;
}
EDIT
通常、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が正しく実装されていることを確認してください。 From 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に等しいという意味ではありません。
Apache Commons Lang にEffective Javaのhashcode()
とequals()
ロジックの良い実装があります。チェックアウト HashCodeBuilder および EqualsBuilder 。
Eclipseを使用している場合は、以下を使用してequals()
とhashCode()
を生成できます。
ソース - > hashCode()とequals()を生成します。
この関数を使用して、等価性とハッシュコードの計算に使用するどのフィールドを決定することができます。Eclipseは対応するメソッドを生成します。
(コードに関して)他のより詳細な回答を完成させるためのちょっとしたメモ:
私が質問 Javaでハッシュテーブルを作成する方法 そして特に jGuru FAQエントリ を考えると、私はハッシュコードを判断できる他の基準は次のとおりです。
私があなたの質問を正しく理解したならば、あなたはカスタムコレクションクラス(すなわち、Collectionインタフェースから拡張する新しいクラス)を持っていて、そしてあなたはhashCode()メソッドを実行したいです。
コレクションクラスがAbstractListを継承する場合は、心配する必要はありません。equals()とhashCode()の実装は、すべてのオブジェクトを繰り返し処理してそれらの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をスキップしてください。見苦しいです。)
あなたが具体的にコレクションを要求したとき、私は他の答えがまだ述べていないという側面を加えたいと思います。全体の目的を打ち負かすだろう...
Apache Commons EqualsBuilder および HashCodeBuilder でリフレクションメソッドを使用してください。
標準的な実装は弱く、それを使うことは不必要な衝突を引き起こします。想像する
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))
List.hashCode
に使用される乗数がここで再利用されるのと同じhashCode
、すなわち31*(a+b) + c
を持ちます。明らかに、衝突は避けられませんが、不必要な衝突を起こすのは単なる...不必要です。
31
を使うことについて実質的に賢いことは何もありません。情報が失われないようにするため、乗数は奇数でなければなりません(偶数乗数であれば少なくとも最上位ビットが失われ、4の倍数で2が失われるなど)。任意の奇数乗数が使用可能です。小さい乗数はより速い計算につながるかもしれません(JITはシフトと加算を使うことができます)が、乗算が現代のIntel/AMDでたった3サイクルの待ち時間を持っているとすれば、これはほとんど問題ではありません。小さな乗数は小さな入力に対してより多くの衝突を引き起こしますが、これは時々問題になるかもしれません。
素数は環Z /(2 ** 32)では意味がないため、素数を使用しても意味がありません。
だから、私はランダムに選ばれた大きな奇数(素数を取ること自由に感じなさい)を使うことを勧めます。 i86/AMD64 CPUは、1つの符号付きバイトに収まるオペランドに短い命令を使用できるため、109のような乗算器では速度が非常に遅くなります。衝突を最小限に抑えるには、0x58a54cf5のようにします。
さまざまな場所でさまざまな乗数を使用することは有用ですが、おそらく追加の作業を正当化するのに十分ではありません。
ハッシュ値を可能な範囲に均等に分散させるハッシュ方法は、優れた実装です。有効なJavaを参照してください( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+Java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxresl========================をご覧ください。 = 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());
}
}
MクラスObjectsのGoogle Collections libからユーティリティメソッドを使用することをお勧めします。これにより、コードをきれいに保つことができます。 equals
およびhashcode
メソッドはIDEのテンプレートから作成されることが非常に多いので、それらを読むのはきれいではありません。
Arrays.deepHashCode(...)
という小さなラッパーを使用しています。これは、パラメータとして提供された配列を正しく処理するためです。
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
ハッシュ値を組み合わせるときは、通常、boost c ++ライブラリで使われている組み合わせ方法を使う。
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
これは、均等な配布を確実にするというかなり良い仕事をします。この式がどのように機能するかについては、StackOverflowの投稿を参照してください。 boost :: hash_combineのマジックナンバー
さまざまなハッシュ関数の良い議論があります: http://burtleburtle.net/bob/hash/doobs.html