JAvaカスタムequals関数とhashCode関数
10494 ワード
すべてのクラスはObjectクラスから継承され、彼のすべての非finalメソッド:equals、hashCode、toString、clone、finalizeは、共通の約束を持っています.これらの方法を上書きするときは、これらの約束に従う必要があります.そうしないと、これらの約束に依存するクラス(例えば、HashMapおよびHashSet)は、クラスと組み合わせて動作しません.
一.equals
等しい概念:
論理的に等しい:たとえばIntegerに含まれる数値が等しい場合、この2つのIntegerは等しいと考えます.例えば、AbstractListでは、2つのlistに含まれるすべての要素が等しい場合、2つのListが等しい.
本当の意味での等しい:同じオブジェクトを指します.
equals関数を再ロードしない場合、2つのクラスの等しいことは本当の意味でのequalにすぎません.クラスが自分の等しい論理を望む場合はInteger/Listのようにequals関数を再ロードする必要があります.
JAva仕様におけるequalsメソッドの特徴
自己反転性:非空参照xの場合、x.equals(x)はtrueを返します.
対称性:任意の参照x、yに対して、y.equals(x)がtrueを返す場合のみ、x.equals(y)がtrueを返す場合.
伝達性:任意の参照x,y,zに対して、x.equals(y)がtrueを返すと、y.equals(z)がtrueを返す.x.equals(z)はtrueを返します.
コンシステンシ:xとyが参照するオブジェクトが変更されていない場合、x.equals(y)を繰り返し呼び出すと同じ結果が返される.
任意の非空参照xに対して、x.equals(null)はfalseを返す.
この問題は2つの異なる状況で見ることができます.
サブクラスが独自の等しい概念を持つことができる場合、対称性需要はgetClassを強制的に採用して検出する.
スーパークラスによって等しい概念が決定されるとinstanceofで検出され,サブクラスを用いないオブジェクト間で等しい比較が可能となる.
TimeStampの非対称性
Date date = new Date();
Timestamp t1 = new Timestamp(date.getTime());
System.out.println("Date equals Timestamp ? : " + date.equals(t1));// true
System.out.println("Timestamp equals Date ? : " + t1.equals(date));// false
TimeStampソース:(getClass()ではなくinstanceofを使用)
// Timestamp
@Override
public boolean equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else {
return false;// Timestamp false
}
}
//
public boolean equals(Timestamp ts) {
if (super.equals(ts)) {
if (nanos == ts.nanos) {
return true;
} else {
return false;
}
} else {
return false;
}
}
親Date:
// Date
@Override
public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
コメント:
equalsメソッドの推奨事項を記述します.
二、hashCode()
設計の原則には、equalsを上書きするときはhashCodeを上書きすることがあります.
hashCode符号化の原則:
1.オブジェクトequalsメソッドの比較操作に使用される情報が変更されていない限り、hashCodeメソッドは同じ整数を返す必要があります.同じアプリケーションの複数回の実行中に、実行ごとに返される整数が一致しなくてもよい.
2.equals(Object)メソッドに従って2つのオブジェクトが等しい場合、hashCodeの戻り値は同じです.
3.両オブジェクトがequals(Object)メソッドに従って比較が異なる場合、両オブジェクトのhashCode戻り値は必ずしも等しくないとは限らないが、異なるオブジェクトに異なる整数結果を生じさせ、ハッシュ・リストの性能を向上させることができる.
具体例
クラスがequalsを上書きしてequals関数を上書きしているのにhashCodeを上書きしていない場合は、上記の第2の原則に違反します.次にhashCodeをリロードしていない例を見てみましょう.
public class PhoneNumber {
private final int areaCode;
private final int prefix;
private final int lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
if(arg < 0 || arg > max) {
throw new IllegalArgumentException(name + ": " + arg);
}
}
@Override
public boolean equals(Object o) {
if(o == this)
return true;
if(!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
}
次のコードを実行します.
Map map = new HashMap();
map.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(map.get(new PhoneNumber(707, 867, 5309)));
Jennyに戻ることを期待していますがnullを返します.
なぜならhashCodeの約束に違反し、PhoneNumberがhashCodeメソッドをカバーしていないため、2つの等しいインスタンスが等しくないハッシュコードを有し、putメソッドは電話番号オブジェクトを1つのハッシュバケツに配置し、getメソッドは別のハッシュバケツからこの電話番号の所有者を検索し、明らかに見つからないからである.
hashCodeをカバーして約束を守れば、この問題を修正することができます.
良いハッシュ関数は「等しくないオブジェクトに等しくないハッシュコードを生成する」傾向があり、以下に簡単な解決策があります.
1.resultというintタイプの変数に、17のようなゼロでない定数値を保存します.(2.aで計算されるハッシュ値が0の初期ドメインのためにハッシュ値に影響します)
2.オブジェクト内の各キードメインfについて、次の手順を完了する.
a.このドメインのintタイプのハッシュコードcを計算する
i.ドメインがbooleanの場合、計算(f?1:0)
ii.ドメインがbyte、char、shortまたはintタイプである場合、計算(int)f
iii.ドメインがlongである場合、計算(int)(f^(f>>>32))
iv.ドメインがfloatである場合、Float.floatToIntBits(f)を計算する
v.ドメインがdoubleの場合、Double.doubleToLongBits(f)を計算し、
vi.ドメインがオブジェクト参照であり、クラスのequalsメソッドがequalsを再帰的に呼び出すことによってこのドメインを比較する場合、hashCodeは同様にこのドメインに再帰的に呼び出される.より複雑な比較が必要な場合は、このドメインの「パターン」を計算し、この「パターン」に対してhashCodeを呼び出します.ドメインの値がnullの場合、0(または通常は0)が返されます.
vii.ドメインが配列である場合、各要素を個別のドメインとして処理する、すなわち、上記のルールを再帰的に適用し、重要な要素ごとにハッシュコードを計算し、2.bに基づいてこれらのハッシュ値を組み合わせる.配列ドメインの各要素が重要である場合、1.5で追加されたArray.hashCodeメソッドの1つを使用できます.
b.次の式に従って、ステップ2.aで計算したハッシュコードcをresultにマージする.
result = 31 * result + c.(31を選択したのは奇素数であり、乗数が偶数の場合、乗算オーバーフロー時に情報が失われるため、VMは31*i=(i<<5)-iを最適化できる)
3.resultを返します.
hashCodeメソッドを記述した後、記述ユニットテストは、同じインスタンスに等しいハッシュコードがあるかどうかを検証する.
上記の解決方法をPhoneNumberクラスに適用します.
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
今までのテストコードを使って、Jennyに戻ることができることを発見しました.