Eclipseでequals()およびhashCode()を生成します
1. 前書き
この記事では、Eclipse IDEを使用してequals()メソッドとhashCode()メソッドを生成する方法について説明します。 Eclipseのコード自動生成がいかに強力で便利であるかを説明し、コードの入念なテストが依然として必要であることも強調します。
2. 規則
Javaのequals()は、2つのオブジェクトが同等であるかどうかを確認するために使用されます。 これをテストする良い方法は、オブジェクトが対称的、再帰的、推移的であることを確認することです。 つまり、3つの非nullオブジェクトa、b、およびcの場合:
-
対称-b.equals(a)の場合にのみa.equals(b)
-
反射–a.equals(a)
-
推移的–a.equals(b)およびb.equals(c)の場合、a.equals(c)
hashCode()は次の1つのルールに従う必要があります。
-
equals()である2つのオブジェクトは、同じhashCode()値を持っている必要があります
3. プリミティブのあるクラス
プリミティブメンバー変数のみで構成されるJavaクラスについて考えてみましょう。
public class PrimitiveClass {
private boolean primitiveBoolean;
private int primitiveInt;
// constructor, getters and setters
}
Eclipse IDEを使用して、「ソース→生成hashCode()およびequals()」を使用してequals()およびhashCode()を生成します。 Eclipseには、次のようなダイアログボックスがあります。
[すべて選択]を選択すると、すべてのメンバー変数が含まれることを確認できます。
[挿入ポイント]の下にリストされているオプションは、生成されるコードのスタイルに影響することに注意してください。 ここでは、これらのオプションを選択せず、[OK]を選択すると、メソッドがクラスに追加されます。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (primitiveBoolean ? 1231 : 1237);
result = prime * result + primitiveInt;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
PrimitiveClass other = (PrimitiveClass) obj;
if (primitiveBoolean != other.primitiveBoolean) return false;
if (primitiveInt != other.primitiveInt) return false;
return true;
}
生成されたhashCode()メソッドは、素数(31)の宣言で始まり、プリミティブオブジェクトに対してさまざまな操作を実行し、オブジェクトの状態に基づいてその結果を返します。
equals()は、最初に2つのオブジェクトが同じインスタンス(==)であるかどうかを確認し、同じ場合はtrueを返します。
次に、比較オブジェクトが非nullであり、両方のオブジェクトが同じクラスであることを確認し、そうでない場合はfalseを返します。
最後に、equals()は各メンバー変数の同等性をチェックし、それらのいずれかが等しくない場合はfalseを返します。
したがって、簡単なテストを書くことができます。
PrimitiveClass aObject = new PrimitiveClass(false, 2);
PrimitiveClass bObject = new PrimitiveClass(false, 2);
PrimitiveClass dObject = new PrimitiveClass(true, 2);
assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());
assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());
4. コレクションとジェネリックスのクラス
それでは、コレクションとジェネリックを備えたより複雑なJavaクラスについて考えてみましょう。
public class ComplexClass {
private List> genericList;
private Set integerSet;
// constructor, getters and setters
}
ここでも、Eclipseを使用します。「ソース」→「hashCode()とequals()'.を生成」ダイアログのEclipseオプション。 我々が得る:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((genericList == null)
? 0 : genericList.hashCode());
result = prime * result + ((integerSet == null)
? 0 : integerSet.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof ComplexClass)) return false;
ComplexClass other = (ComplexClass) obj;
if (genericList == null) {
if (other.genericList != null)
return false;
} else if (!genericList.equals(other.genericList))
return false;
if (integerSet == null) {
if (other.integerSet != null)
return false;
} else if (!integerSet.equals(other.integerSet))
return false;
return true;
}
生成されたhashCode()メソッドは、AbstractList.hashCode()およびAbstractSet.hashCode()コアJavaメソッドに依存しています。 これらはコレクションを反復処理し、各アイテムのhashCode()値を合計して、結果を返します。
同様に、生成されたequals()メソッドはAbstractList.equals()とAbstractSet.equals()を使用し、フィールドを比較することでコレクションが等しいかどうかを比較します。
いくつかの例をテストすることで、堅牢性を検証できます。
ArrayList strArrayList = new ArrayList();
strArrayList.add("abc");
strArrayList.add("def");
ComplexClass aObject = new ComplexClass(strArrayList, new HashSet(45,67));
ComplexClass bObject = new ComplexClass(strArrayList, new HashSet(45,67));
ArrayList strArrayListD = new ArrayList();
strArrayListD.add("lmn");
strArrayListD.add("pqr");
ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet(45,67));
assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());
assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());
5. 継承
継承を使用するJavaクラスについて考えてみましょう。
public abstract class Shape {
public abstract double area();
public abstract double perimeter();
}
public class Rectangle extends Shape {
private double width;
private double length;
@Override
public double area() {
return width * length;
}
@Override
public double perimeter() {
return 2 * (width + length);
}
// constructor, getters and setters
}
public class Square extends Rectangle {
Color color;
// constructor, getters and setters
}
Squareクラスで 'Source→GeneratehashCode() andequals()'を試みると、Eclipseは 'スーパークラス' Rectangle 'がequals()とhashCode():結果のコードが正しく機能しない可能性があります。
同様に、RectangleクラスでhashCode()とequals()を生成しようとすると、スーパークラス「Shape」に関する警告が表示されます。
Eclipseは警告にもかかわらず、私たちが前進することを可能にします。 Rectangleの場合、具体的なメンバー変数がないため、hashCode()またはequals()を実装できない抽象Shapeクラスを拡張します。 その場合、Eclipseは無視できます。
ただし、Squareクラスは、Rectangleからwidthおよびlengthメンバー変数と、それ自体の色変数を継承します。 最初にRectangleに対して同じことを行わずに、SquareにhashCode()とequals()を作成することは、equals() /hashCode()にcolorのみを使用することを意味します。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((color == null) ? 0 : color.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;
Square other = (Square) obj;
if (color == null) {
if (other.color != null)
return false;
} else if (!color.equals(other.color))
return false;
return true;
}
簡単なテストでは、widthはwidthに含まれていないため、widthだけが異なる場合、Squareのequals() /hashCode()は十分ではないことがわかります。 t5)s /hashCode()の計算:
Square aObject = new Square(10, Color.BLUE);
Square dObject = new Square(20, Color.BLUE);
Assert.assertFalse(aObject.equals(dObject));
Assert.assertFalse(aObject.hashCode() == dObject.hashCode());
Eclipseを使用してRectangleクラスのequals() /hashCode()を生成することにより、これを修正しましょう。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(length);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(width);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Rectangle other = (Rectangle) obj;
if (Double.doubleToLongBits(length)
!= Double.doubleToLongBits(other.length)) return false;
if (Double.doubleToLongBits(width)
!= Double.doubleToLongBits(other.width)) return false;
return true;
}
Squareクラスでequals() /hashCode()を再生成する必要があるため、Rectangleのequals() /hashCode()が呼び出されます。 この世代のコードでは、Eclipseダイアログですべてのオプションを選択したため、コメント、instanceOfの比較、およびifブロックが表示されます。
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((color == null) ? 0 : color.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (!(obj instanceof Square)) {
return false;
}
Square other = (Square) obj;
if (color == null) {
if (other.color != null) {
return false;
}
} else if (!color.equals(other.color)) {
return false;
}
return true;
}
上からテストを再実行すると、SquareのhashCode() /equals()が正しく計算されているため、合格です。
6. 結論
Eclipse IDEは非常に強力であり、ボイラープレートコード(ゲッター/セッター、さまざまなタイプのコンストラクター、equals()、およびhashCode())の自動生成を可能にします。
Eclipseが何をしているかを理解することにより、これらのコーディングタスクに費やす時間を削減できます。 ただし、予想されるすべてのケースを処理したことを確認するには、引き続き注意を払い、テストでコードを検証する必要があります。
コードスニペットは、いつものように、over on GitHubで見つけることができます。