サブクラスを考慮してスーパーフィールドでequals()
&hashCode()
をオーバーライドする方法に特定のルールはありますか?多くのパラメータがあることを知っている:スーパーフィールドはprivate/publicであり、ゲッターの有無にかかわらず...
たとえば、Netbeansはequals()およびhashCode()を生成し、スーパーフィールドを考慮しません...および
new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))
trueを返します:(
public class Hominidae {
public String gender;
public String weight;
public String height;
public Hominidae(String gender, String weight, String height) {
this.gender = gender;
this.weight = weight;
this.height = height;
}
...
}
public class HomoSapiens extends Hominidae {
public String name;
public String faceBookNickname;
public HomoSapiens(String gender, String weight, String height,
String name, String facebookId) {
super(gender, weight, height);
this.name = name;
this.faceBookNickname = facebookId;
}
...
}
Netbeansが生成したequals()およびhashCode()を表示する場合:
public class Hominidae {
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Hominidae other = (Hominidae) obj;
if ((this.gender == null) ? (other.gender != null) : !this.gender.equals(other.gender)) {
return false;
}
if ((this.weight == null) ? (other.weight != null) : !this.weight.equals(other.weight)) {
return false;
}
if ((this.height == null) ? (other.height != null) : !this.height.equals(other.height)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 37 * hash + (this.gender != null ? this.gender.hashCode() : 0);
hash = 37 * hash + (this.weight != null ? this.weight.hashCode() : 0);
hash = 37 * hash + (this.height != null ? this.height.hashCode() : 0);
return hash;
}
}
public class HomoSapiens extends Hominidae {
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HomoSapiens other = (HomoSapiens) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.faceBookNickname == null) ? (other.faceBookNickname != null) : !this.faceBookNickname.equals(other.faceBookNickname)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
return hash;
}
}
子どもは親の私的メンバーを調べるべきではありません
しかし、明らかに、すべての重要なフィールドは、等価性とハッシュのために考慮されるべきです。
幸いなことに、両方のルールを簡単に満たすことができます。
NetBeansが生成したequalsとハッシュコードを使用して動けなくなると仮定すると、Hominidaeのequalsメソッドを変更して、クラスの等価性ではなくinstanceof比較を使用し、そのまま使用できます。このようなもの:
@Override
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
if (! super.equals(obj)) return false;
else {
// compare subclass fields
}
もちろん、ハッシュコードは簡単です。
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
return hash;
}
しかし、真剣に:スーパークラスメソッドを呼び出してスーパークラスフィールドを考慮に入れていないNetBeansはどうなっていますか?
私は commons-langパッケージ の EqualsBuilder (およびHashcodeBuilder)を使用して、equals()およびhashcode()メソッドを読みやすくすることを好みます。
例:
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) {
return false;
}
MyClass rhs = (MyClass) obj;
return new EqualsBuilder()
.appendSuper(super.equals(obj))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
一般的に、サブクラス間で等値を実装することは、対称的かつ推移的に維持するのが困難です。
フィールドx
およびy
をチェックするスーパークラス、およびx
、y
およびz
をサブクラスがチェックすることを検討してください。
したがって、サブクラスの最初のインスタンスと2番目のインスタンスでzが異なるサブクラス==スーパークラス==サブクラスは、契約の推移部分に違反します。
これが、equalsの典型的な実装がinstanceofを行う代わりにgetClass() != obj.getClass()
をチェックする理由です。上記の例では、SubClassまたはSuperclassがinstanceofチェックを行うと、対称性が失われます。
つまり、サブクラスは確かにsuper.equals()を考慮に入れることができますが、上記の問題を回避するために独自のgetClass()チェックも実行し、さらに独自のフィールドで等価をチェックする必要があります。スーパークラスが等しいを返すだけでなく、スーパークラスの特定のフィールドに基づいて独自の同等の動作を変更したクラスの奇妙なカモだろう。
ルールは次のとおりです。
from Object.equals() .
したがって、ルールを満たすために必要なフィールドを使用してください。
継承はカプセル化を破壊するため、equals()およびhashCode()を実装するサブクラスは、必然的に、スーパークラスの特性を説明する必要があります。サブクラスのメソッドから親クラスのequals()およびhashCode()メソッドへの呼び出しをエンコードすることに成功しました。
受け入れられた@CPerkinsの回答に関しては、super.equals()メソッドもクラスの同等性をチェックする可能性があるため、指定されたequals()コードが確実に機能するとは思いません。サブクラスとスーパークラスには同等のクラスはありません。
さて、HomoSapiens#hashcode
はCPerkinsの答えで十分です。
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 89 * hash + Objects.hash(name);
hash = 89 * hash + Objects.hash(faceBookNickname);
return hash;
}
それらの親のフィールド(gender
、weight
、height
)を実行したい場合、1つの方法は親タイプの実際のインスタンスを作成して使用することです。神に感謝します。抽象クラスではありません。
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HomoSapiens other = (HomoSapiens) obj;
if (!super.equals(new Hominidae(
other.gender, other.weight, other.height))) {
return false;
}
if (!Objects.equals(name, other.name)) return false;
if (!Objects.equals(faceBookNickname, other.faceBookNickname))
return false;
return true;
}
私はこれを解決する(と思う)方法を追加しています。重要な点は、メソッドを追加して同等性を大まかにチェックすることです。
public class Parent {
public Parent(final String name) {
super(); this.name = name;
}
@Override
public int hashCode() {
return hash = 53 * 7 + Objects.hashCode(name);
}
@Override
public boolean equals(final Object obj) {
return equalsAs(obj) && getClass() == obj.getClass();
}
protected boolean equalsAs(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!getClass().isAssignableFrom(obj.getClass())) return false;
final Parent other = (Parent) obj;
if (!Objects.equals(name, other.name)) return false;
return true;
}
private final String name;
}
そして、ここにChild
があります。
public class Child extends Parent {
public Child(final String name, final int age) {
super(name); this.age = age;
}
@Override
public int hashCode() {
return hash = 31 * super.hashCode() + age;
}
@Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
@Override
protected boolean equalsAs(final Object obj) {
if (!super.equalsAs(obj)) return false;
if (!getClass().isAssignableFrom(obj.getClass())) return false;
final Child other = (Child) obj;
if (age != other.age) return false;
return true;
}
private final int age;
}
テスト中...
@Test(invocationCount = 128)
public void assertReflective() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
assertTrue(x.equals(x));
assertEquals(x.hashCode(), x.hashCode());
}
@Test(invocationCount = 128)
public void assertSymmetric() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
final Child y = new Child(name, age);
assertTrue(x.equals(y));
assertEquals(x.hashCode(), y.hashCode());
assertTrue(y.equals(x));
assertEquals(y.hashCode(), x.hashCode());
}
@Test(invocationCount = 128)
public void assertTransitive() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
final Child y = new Child(name, age);
final Child z = new Child(name, age);
assertTrue(x.equals(y));
assertEquals(x.hashCode(), y.hashCode());
assertTrue(y.equals(z));
assertEquals(y.hashCode(), z.hashCode());
assertTrue(x.equals(z));
assertEquals(x.hashCode(), z.hashCode());
}
IDE自動生成はスーパークラスを考慮している可能性がありますが、スーパークラスのequals()およびhashCode()がまだ存在する場合に限ります。つまり、これら2つを自動生成する必要があります。最初にスーパーの関数、次に子の自動生成を行います。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TActivityWrapper that = (TActivityWrapper) o;
return data != null ? data.equals(that.data) : that.data == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (data != null ? data.hashCode() : 0);
return result;
}
この問題は、最初にスーパーを自動生成しないときに発生します。 Netbeansで上記を確認してください。
親(スーパー)クラスがequalsをオーバーライドしないようです。この場合、サブクラスでこのメソッドをオーバーライドするときに、親クラスのフィールドを比較する必要があります。コモンズEqualsBuilerを使用するのがよい方法であることに同意しますが、イコールコントラクトの対称/遷移部分を壊さないように注意する必要があります。
サブクラスが親クラスに属性を追加し、親クラスが抽象的ではなく、等しいものをオーバーライドする場合、問題が発生します。このシナリオでは、継承ではなくオブジェクトの構成を実際に見る必要があります。
これについては、Joshua BlockによるEffective Javaのセクション。
私は彼らが今あなたのためだけにこれを行う方法を持っていると信じています:
EqualsBuilder.reflectionEquals(this、o);
HashCodeBuilder.reflectionHashCode(this);