What issues should be considered when overriding equals and hashCode in Java?
当覆盖
理论(对于语言律师和数学倾向者而言):
这两种方法之间的关系是:
Whenever
a.equals(b) , thena.hashCode() must be same asb.hashCode() .
在实践中:
如果您覆盖其中一个,那么您应该覆盖另一个。
使用与计算
使用来自ApacheCommonsLang库的优秀助手类EqualsBuilder和HashCodeBuilder。一个例子:
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 | public class Person { private String name; private int age; // ... @Override public int hashCode() { return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers // if deriving: appendSuper(super.hashCode()). append(name). append(age). toHashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) return false; if (obj == this) return true; Person rhs = (Person) obj; return new EqualsBuilder(). // if deriving: appendSuper(super.equals(obj)). append(name, rhs.name). append(age, rhs.age). isEquals(); } } |
还记得:
使用基于哈希的集合或映射(如哈希集、LinkedHashSet、HashMap、HashTable或WeakHashMap)时,请确保在对象位于集合中时,放入集合中的关键对象的HashCode()不会更改。确保这一点的防弹方法是使您的密钥不变,这也有其他好处。
如果您正在处理使用对象关系映射器(ORM)持久化的类(如Hibernate),那么有一些问题值得注意,如果您不认为这已经非常复杂了!
延迟加载的对象是子类
如果使用ORM持久化对象,在许多情况下,您将处理动态代理,以避免过早地从数据存储加载对象。这些代理作为您自己的类的子类实现。这意味着
1 2 3 4 5 | Person saved = new Person("John Doe"); Long key = dao.save(saved); dao.flush(); Person retrieved = dao.retrieve(key); saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy |
如果您正在处理一个ORM,那么使用
延迟加载的对象具有空字段
窗体通常使用getter强制加载延迟加载的对象。这意味着,如果
如果您处理的是ORM,请确保始终使用getter,并且不要在
保存对象将更改其状态
持久对象通常使用
我经常使用的模式是
1 2 3 4 5 6 | if (this.getId() == null) { return this == other; } else { return this.getId().equals(other.getId()); } |
但是:你不能把
在我的
关于
这一说法是
请考虑以下省略语句时发生的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class A { int field1; A(int field1) { this.field1 = field1; } public boolean equals(Object other) { return (other != null && other instanceof A && ((A) other).field1 == field1); } } class B extends A { int field2; B(int field1, int field2) { super(field1); this.field2 = field2; } public boolean equals(Object other) { return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other)); } } |
在执行
这看起来很好,但是如果我们尝试使用这两个类,会发生什么:
1 2 3 4 | A a = new A(1); B b = new B(1,1); a.equals(b) == true; b.equals(a) == false; |
显然,这是错误的。
如果要确保对称条件。a=b如果b=a和liskov替换原则不仅在
1 2 3 4 | if (other instanceof B ) return (other != null && ((B)other).field2 == field2 && super.equals(other)); if (other instanceof A) return super.equals(other); else return false; |
将输出:
1 2 | a.equals(b) == true; b.equals(a) == true; |
如果
对于易于继承的实现,请查看Tal-Cohen的解决方案,如何正确实现equals()方法?
总结:
在他的书《有效Java编程语言指南》(Addison Wesley,2001)中,Joshua Bloch声称:"根本没有办法扩展一个可实例化的类,并且在保存等值契约的同时添加一个方面。"TAL不同意。
他的解决方案是通过双向调用另一个非对称的blindlyequals()来实现equals()。BlindlyEquals()被子类重写,Equals()被继承,并且从不重写。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Point { private int x; private int y; protected boolean blindlyEquals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return (p.x == this.x && p.y == this.y); } public boolean equals(Object o) { return (this.blindlyEquals(o) && o.blindlyEquals(this)); } } class ColorPoint extends Point { private Color c; protected boolean blindlyEquals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint)o; return (super.blindlyEquals(cp) && cp.color == this.color); } } |
注意,如果要满足Liskov替换原则,equals()必须跨继承层次结构工作。
仍然令人惊讶的是,没有人为此推荐了番石榴图书馆。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //Sample taken from a current working project of mine just to illustrate the idea @Override public int hashCode(){ return Objects.hashCode(this.getDate(), this.datePattern); } @Override public boolean equals(Object obj){ if ( ! obj instanceof DateAndPattern ) { return false; } return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate()) && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern()); } |
在超级类中有两个方法,即java.lang.object。我们需要将它们重写为自定义对象。
1 2 |
相等的对象必须产生相同的哈希代码,只要它们相等,但是不相等的对象不需要产生不同的哈希代码。
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 Test { private int num; private String data; public boolean equals(Object obj) { if(this == obj) return true; if((obj == null) || (obj.getClass() != this.getClass())) return false; // object must be Test at this point Test test = (Test)obj; return num == test.num && (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { int hash = 7; hash = 31 * hash + num; hash = 31 * hash + (null == data ? 0 : data.hashCode()); return hash; } // other methods } |
如果您想了解更多信息,请查看以下链接:http://www.javaranch.com/journal/2002/10/equalhash.html
这是另一个例子,http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareto-java-method.html
玩得高兴!@
在检查成员相等之前,有几种方法可以检查类相等性,我认为这两种方法在正确的情况下都有用。
我在
选项2允许安全地扩展类,而不覆盖等号或破坏对称性。
如果您的类也是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | final class MyClass implements Comparable<MyClass> { … @Override public boolean equals(Object obj) { /* If compareTo and equals aren't final, we should check with getClass instead. */ if (!(obj instanceof MyClass)) return false; return compareTo((MyClass) obj) == 0; } } |
对于平等者,请看安吉丽卡·兰格的《平等的秘密》。我非常喜欢它。她也是Java泛型的一个很好的常见问题。查看她的其他文章(向下滚动到"核心Java"),在那里她还继续进行第二部分和"混合类型比较"。阅读它们很有趣!
equals()方法用于确定两个对象的相等性。
因为int值10总是等于10。但这个equals()方法是关于两个对象的相等性。当我们说对象时,它将具有属性。为了确定相等性,需要考虑这些属性。不必考虑所有属性来确定相等性,并且可以根据类定义和上下文来确定相等性。然后可以重写equals()方法。
每当重写equals()方法时,我们都应该重写hashcode()方法。如果没有,会发生什么?如果我们在应用程序中使用哈希表,它将不会像预期的那样工作。由于哈希代码用于确定存储值的相等性,因此它不会返回键的正确对应值。
给定的默认实现是object类中的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 | public class Tiger { private String color; private String stripePattern; private int height; @Override public boolean equals(Object object) { boolean result = false; if (object == null || object.getClass() != getClass()) { result = false; } else { Tiger tiger = (Tiger) object; if (this.color == tiger.getColor() && this.stripePattern == tiger.getStripePattern()) { result = true; } } return result; } // just omitted null checks @Override public int hashCode() { int hash = 3; hash = 7 * hash + this.color.hashCode(); hash = 7 * hash + this.stripePattern.hashCode(); return hash; } public static void main(String args[]) { Tiger bengalTiger1 = new Tiger("Yellow","Dense", 3); Tiger bengalTiger2 = new Tiger("Yellow","Dense", 2); Tiger siberianTiger = new Tiger("White","Sparse", 4); System.out.println("bengalTiger1 and bengalTiger2:" + bengalTiger1.equals(bengalTiger2)); System.out.println("bengalTiger1 and siberianTiger:" + bengalTiger1.equals(siberianTiger)); System.out.println("bengalTiger1 hashCode:" + bengalTiger1.hashCode()); System.out.println("bengalTiger2 hashCode:" + bengalTiger2.hashCode()); System.out.println("siberianTiger hashCode:" + siberianTiger.hashCode()); } public String getColor() { return color; } public String getStripePattern() { return stripePattern; } public Tiger(String color, String stripePattern, int height) { this.color = color; this.stripePattern = stripePattern; this.height = height; } } |
代码输出示例:
1 2 3 4 5 | bengalTiger1 and bengalTiger2: true bengalTiger1 and siberianTiger: false bengalTiger1 hashCode: 1398212510 bengalTiger2 hashCode: 1398212510 siberianTiger hashCode: –1227465966 |
逻辑上我们有:
但反之亦然!
我发现的一个问题是,两个对象包含彼此的引用(一个例子是父/子关系,父对象上有一个方便的方法来获取所有子对象)。例如,在进行Hibernate映射时,这类事情相当常见。
如果将关系的两端都包含在哈希代码或等于测试中,则可以进入以StackOverflowException结尾的递归循环。最简单的解决方案是不要在方法中包含getchildren集合。