在子类中重写equals()&hashCode()…考虑超级域
是否有一个具体的规则如何覆盖equals()
和hashCode()
在考虑超级字段的 子类 ? 知道有很多参数:超级字段是私人/公共,有/没有getter …
例如,Netbeans生成的equals()和hashCode()不会考虑超级字段…和
new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals( new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))
将返回真实:(
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和hashcode卡住,您可以修改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 }
当然,hashcode很容易:
@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
。
所以一个Subclass == Superclass == Subclass其中z在子类的第一个实例和第二个实例之间是不同的,违反了合约的传递部分。
这就是为什么equals的典型实现将检查getClass() != obj.getClass()
而不是执行instanceof。 在上面的例子中,如果SubClass或者Superclass做一个instanceof检查,它会破坏对称性。
所以结果是一个子类当然可以考虑到super.equals(),但也应该做自己的getClass()检查,以避免上述问题,然后再检查自己的领域的平等。 这将是一个奇怪的鸭类,它根据超类的特定字段而不是仅仅当超类返回等于时才改变它自己的等号行为。
规则是:
- 它是自反的:对于任何非空引用值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)的多个调用始终返回true或一致地返回false。
- 对于任何非null的引用值x,x.equals(null)应该返回false。
- 通常需要重写hashCode方法,只要这个方法被覆盖,为了维护hashCode方法的一般约定,它声明了相等的对象必须有相同的散列码
来自Object.equals() 。
所以,使用所需的字段来完成规则。
因为inheritance破坏了封装,所以实现equals()和hashCode()的子类必须考虑到它们的超类的特性。 我已经成功地从子类的方法中调用父类的equals()和hashCode()方法。
关于接受的@CPerkins答案,我不认为给定的equals()代码将可靠地工作,因为super.equals()方法也可能检查类相等的可能性。 一个子类和超类不会有相同的类。
这听起来像你的父(超)类不覆盖等于。 如果是这种情况,那么当在子类中重写此方法时,您需要比较父类的字段。 我同意使用EqualsBuiler的公共方法是可行的,但是您必须小心,不要打破等于合约的对称/可变部分。
如果你的子类为父类添加属性,而父类不是抽象的,并且覆盖平等,你将会陷入困境。 在这种情况下,你应该看看对象的组成,而不是inheritance。
我强烈推荐Joshua Block的Effective Java一节。 这是全面的,很好地解释。
那么,在CPerkins的回答中, HomoSapiens#hashcode
就足够了。
@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
),一种方法是创build父types的实际实例并使用它。 感谢上帝,这不是一个抽象的类。
@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; }
testing…
@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()); }
值得注意的是,只要存在超类的equals()和hashCode(),IDE自动生成可能已经考虑了超类。 也就是说,应该自动生成超级优先这两个函数,然后自动生成subprocess。 我在Intellj Idea下得到了正确的例子:
@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下面检查。