Eclipseのソースメニューには、以下のような関数を生成する「generate hashCode/equalsメソッド」があります。
_String name;
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CompanyRole other = (CompanyRole) obj;
if (name == null)
{
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
_
hashCode()
とequals()
を生成するときに複数のフィールドを選択すると、Eclipseは上記と同じパターンを使用します。
私はハッシュ関数の専門家ではありません。生成されたハッシュ関数がどれほど「優れている」か知りたいですか?それが故障して衝突が多すぎる状況は何ですか?
HashCode関数の実装はJava.util.ArrayList
で次のように確認できます。
public int hashCode() {
int hashCode = 1;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
これはそのような例の1つであり、Eclipseで生成されたコードはそれを実装する同様の方法に従います。しかし、hashCodeを自分で実装する必要があると感じた場合は、Joshua Blochの有名な本 Effective Java にいくつかの優れたガイドラインがあります。その本の項目9からそれらの重要なポイントを投稿します。それらは、
- 定数の非ゼロ値、たとえば17をresultというint変数に格納します。
オブジェクトの重要なフィールドfごとに(つまり、equalsメソッドによって考慮される各フィールド)、次のようにします。
a。フィールドのintハッシュコードcを計算します。
私。フィールドがブール値の場合、(f?1:0)を計算します。
ii。フィールドがbyte、char、short、またはintの場合、(int)fを計算します。
iii。フィールドが長い場合は、(int)(f ^(f >>> 32))を計算します。
iv。フィールドがfloatの場合、Float.floatToIntBits(f)を計算します。
v。フィールドがdoubleの場合、Double.doubleToLongBits(f)を計算し、ステップ2.a.iiiのように結果のlongをハッシュします。
vi。フィールドがオブジェクト参照であり、このクラスのequalsメソッドがequalsを再帰的に呼び出すことによってフィールドを比較する場合、フィールドでhashCodeを再帰的に呼び出します。より複雑な比較が必要な場合は、このフィールドの「正規表現」を計算し、正規表現でhashCodeを呼び出します。フィールドの値がnullの場合、0を返します(または他の定数ですが、0は従来の方法です)
vii。フィールドが配列の場合は、各要素が個別のフィールドであるかのように扱います。つまり、これらのルールを再帰的に適用して重要な要素ごとにハッシュコードを計算し、ステップ2.bに従ってこれらの値を組み合わせます。配列フィールドのすべての要素が重要である場合は、リリース1.5で追加されたArrays.hashCodeメソッドの1つを使用できます。
b。手順2.aで計算されたハッシュコードcを次のように結果に結合します。
result = 31 * result + c;
結果を返します。
HashCodeメソッドの記述が終了したら、等しいインスタンスに等しいハッシュコードがあるかどうかを自問してください。あなたの直感を検証するためにユニットテストを書いてください!等しいインスタンスのハッシュコードが等しくない場合は、その理由を理解して問題を修正してください。
Java言語設計者とEclipseは、私が思う同様のガイドラインに従っているようです。ハッピーコーディング。乾杯。
Java 7なので、Java.util.Objects
を使用して、短くエレガントなメソッドを記述できます。
class Foo {
private String name;
private String id;
@Override
public int hashCode() {
return Objects.hash(name,id);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Foo) {
Foo right = (Foo) obj;
return Objects.equals(name,right.name) && Objects.equals(id,right.id);
}
return false;
}
}
はい、それは完璧です:)このアプローチはJavaソースコードのほとんどどこでも見られます。
Apache Software Foundation(commons-langライブラリ)を使用している場合、以下のクラスは、リフレクションを使用してhashcode/equals/toStringメソッドを生成するのに役立ちます。
インスタンス変数を追加/削除するときに、hashcode/equals/toStringメソッドの再生成について心配する必要はありません。
EqualsBuilder -このクラスは、任意のクラスに適切なequalsメソッドを構築するためのメソッドを提供します。これは、JoshuaBlochによるEffective Java、)に規定されているルールに従います。特に、double、float、および配列を比較するためのルールは注意が必要です。また、equals()とhashCode()を確認してください。一貫していることは難しい場合があります。
HashCodeBuilder -このクラスを使用すると、任意のクラスに対して適切なhashCodeメソッドを構築できます。これは、JoshuaBloch著のEffective Java)に記載されているルールに従います。優れたhashCodeメソッドを作成することは、実際には非常に困難です。このクラスは、プロセスを簡素化することを目的としています。
ReflectionToStringBuilder -このクラスは、リフレクションを使用して、追加するフィールドを決定します。これらのフィールドは通常プライベートであるため、クラスはAccessibleObject.setAccessible(Java.lang.reflect.AccessibleObject []、boolean)を使用してフィールドの可視性を変更します。適切な権限が正しく設定されていない限り、これはセキュリティマネージャでは失敗します。
Mavenの依存関係:
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons.lang.version}</version>
</dependency>
サンプルコード:
import org.Apache.commons.lang.builder.EqualsBuilder;
import org.Apache.commons.lang.builder.HashCodeBuilder;
import org.Apache.commons.lang.builder.ReflectionToStringBuilder;
public class Test{
instance variables...
....
getter/setter methods...
....
@Override
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
@Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
}
また、JoshuaBlochによるEffective Java 2ndEditionの項目9への参照を追加したいと思います。
これがItem 9 : ALWAYS OVERRIDE HASHCODE WHEN YOU OVERRIDE EQUALS
のレシピです
- 結果と呼ばれるint変数に、ゼロ以外の定数値、たとえば17を格納します。
- オブジェクトの重要なフィールドfごとに(つまり、equalsメソッドによって考慮される各フィールド)、次のようにします。
a. Compute an int hash code c for the field:
i. If the field is a boolean, compute (f ? 1 : 0).
ii. If the field is a byte, char, short, or int, compute (int) f.
iii. If the field is a long,compute(int)(f^(f>>>32)).
iv. If the field is a float, compute Float.floatToIntBits(f).
v. If the field is a double, compute Double.doubleToLongBits(f), and then hash the resulting long as in step 2.a.iii.
vi. If the field is an object reference and this class’s equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a “canonical representation” for this field and invoke hashCode on the canonical representation. If the value of the field is null, return 0 (or some other constant, but 0 is traditional).
vii. If the field is an array, treat it as if each element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine these values per step 2.b. If every element in an array field is significant, you can use one of the Arrays.hashCode methods added in release 1.5.
b. Combine the hash code c computed in step 2.a into result as follows: result = 31 * result + c; 3. Return result. 4. When you are finished writing the hashCode method, ask yourself whether equal instances have equal hash codes. Write unit tests to verify your intuition! If equal instances have unequal hash codes, figure out why and fix the problem.
潜在的な欠点の1つは、nullフィールドを持つすべてのオブジェクトのハッシュコードが31になるため、nullフィールドのみを含むオブジェクト間で多くの潜在的な衝突が発生する可能性があることです。これにより、Maps
でのルックアップが遅くなります。
これは、キータイプに複数のサブクラスがあるMap
がある場合に発生する可能性があります。たとえば、HashMap<Object, Object>
、ハッシュコードが31である多くのキー値を持つことができます。確かに、これはそれほど頻繁には発生しません。必要に応じて、素数の値を31以外の値にランダムに変更し、衝突の可能性を減らすことができます。
これは、ハッシュ関数を記述する標準的な方法です。ただし、フィールドに関する知識があれば、改善/簡素化できます。例えば。クラスでフィールドがnullにならないことが保証されている場合は、nullチェックを省略できます(equals()にも適用されます)。または、フィールドが1つだけ使用されている場合は、フィールドのハッシュコードを委任することもできます。