Overriding equals() & hashCode() in sub classes … considering super fields
在考虑超字段的子类中如何覆盖
例如,netbeans generated equals()&;hashcode()不会考虑超级字段…和
1 2 | new HomoSapiens("M","80","1.80","Cammeron","VeryHot").equals( new HomoSapiens("F","50","1.50","Cammeron","VeryHot")) |
将返回true:。(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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 comparison而不是类equality,然后直接使用它。像这样:
1 2 3 4 5 6 7 8 | @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很简单:
1 2 3 4 5 6 7 | @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()方法更易于阅读。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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(); } |
一般来说,跨子类实现equals很难保持对称性和可传递性。
考虑检查字段
因此,子类==superclass==subclass,其中z在子类的第一个实例和第二个实例之间是不同的,违反了契约的可传递部分。
因此,equals的典型实现将检查
因此,结果是子类当然可以考虑super.equals(),但也应该进行自己的getClass()检查,以避免上述问题,然后在自己的字段上检查equals。这将是一个奇怪的类鸭,它根据超类的特定字段改变了自己的等号行为,而不是仅仅当超类返回等号。
规则是:
- 它是自反的:对于任何非空的引用值x,x.equals(x)都应返回true。
- 它是对称的:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)应返回true。
- 它是可传递的:对于任何非空引用值x、y和z,如果x.equals(y)返回真,y.equals(z)返回真,则x.equals(z)应返回真。
- 它是一致的:对于任何非空的引用值x和y,多次调用x.equals(y)始终返回true或始终返回false,前提是不修改在对象的equals比较中使用的信息。
- 对于任何非空引用值x,x.equals(null)应返回false。
- 每当重写此方法时,通常需要重写hashcode方法,以便维护hashcode方法的常规约定,该约定规定相等的对象必须具有相等的哈希代码。
来自object.equals()。
因此,请使用满足规则所需的字段。
关于接受的@cperkins答案,我认为给定的equals()代码不会可靠地工作,因为super.equals()方法还可能检查类的相等性。子类和超类将不具有相同的类。
因为继承破坏了封装,所以实现equals()和hashcode()的子类必须解释它们的超类的特性。我成功地对父类的equals()和hashcode()方法的子类方法进行了编码调用。
值得注意的是,如果super类的equals()和hashcode()已经存在,那么IDE自动生成可能已经考虑了super类。也就是说,应该先自动生成这两个超级函数,然后再自动生成子函数。我在intellj的想法下面得到了正确的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @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; } |
当你不首先自动生成super's时,问题就发生了。请查看以上Netbeans下的内容。
好吧,埃多克司1〔0〕的回答就足够了。
1 2 3 4 5 6 7 | @Override public int hashCode() { int hash = super.hashCode(); hash = 89 * hash + Objects.hash(name); hash = 89 * hash + Objects.hash(faceBookNickname); return hash; } |
如果希望这些父类型的字段(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @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; } |
我正在添加一种解决这个问题的方法。关键是添加一个方法来松散地检查相等性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
江户一号〔4〕来了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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; } |
测试…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | @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。如果是这种情况,则在子类中重写此方法时,需要比较父类中的字段。我同意使用Commons Equalsbuiler是一种可行的方法,但是您需要小心,不要破坏Equals合同的对称/交换部分。
如果您的子类向父类添加属性,而父类不是抽象的,并且重写等于,则会遇到麻烦。在这个场景中,您应该真正关注对象组合而不是继承。
我强烈推荐Joshua Block在这个有效的Java方面。它很全面,解释得很好。
我相信他们现在有了一种方法,可以为你做到这一点:
equalsbuilder.reflectionequals(this,o);
hashcodebuilder.reflectionhashcode(this);