1. 序章

この記事では、EclipseIDEを使用して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を使用して、’Source-> Generate hashCode() and 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<Integer> integerSet;

    // constructor, getters and setters
}

ここでも、Eclipseの[ソース]->[生成]を使用しますハッシュコード() equals()’。 に注意してくださいハッシュコード() 使用 instanceOf ダイアログの[Eclipse]オプションで[Use’instanceof’to compare types’]を選択したため、クラスオブジェクトを比較します。 我々が得る:

@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<String> strArrayList = new ArrayList<String>();
strArrayList.add("abc");
strArrayList.add("def");
ComplexClass aObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
ComplexClass bObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
        
ArrayList<String> strArrayListD = new ArrayList<String>();
strArrayListD.add("lmn");
strArrayListD.add("pqr");
ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet<Integer>(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->Generate hashCode() and equals()‘を試行すると、Eclipseは’スーパークラス’Rectangle’ equals()および hashCode()を再宣言しません:結果のコードは正しく機能しない可能性があります。

同様に、 RectangleクラスでhashCode()および equals()を生成しようとすると、スーパークラス「Shape」に関する警告が表示されます。

Eclipseを使用すると、警告が表示されても前に進むことができます。 Rectangle の場合、 hashCode()または equals()がないため、実装できない抽象Shapeクラスを拡張します。具体的なメンバー変数。 その場合、Eclipseは無視できます。

ただし、 Square クラスは、Rectangleからwidthおよびlengthメンバー変数、およびそれ自体の色変数を継承します。 SquarehashCode() equals()を作成する場合、最初に Rectangle に対して同じことを行わずに、colorのみを使用することを意味します。 ] equals() / hashCode()

@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;
}

簡単なテストでは、 Squareequals() / hashCode()は、 width だけが異なる場合、十分ではないことがわかります。 widthequals() / 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;
}

上からテストを再実行すると、 SquarehashCode() / equals()が正しく計算されているため、合格です。

6. 結論

Eclipse IDEは非常に強力で、ボイラープレートコード(ゲッター/セッター、さまざまなタイプのコンストラクター、 equals()、および hashCode())の自動生成を可能にします。

Eclipseが何をしているのかを理解することで、これらのコーディングタスクに費やす時間を減らすことができます。 ただし、予想されるすべてのケースを処理したことを確認するために、注意を払い、テストでコードを検証する必要があります。

コードスニペットは、いつものように、GitHubにあります。