在Java中重写equals和hashCode时应该考虑什么问题?

当重写equalshashCode时,必须考虑哪些问题/陷阱?

理论(针对语言律师和math倾向):

equals() ( javadoc )必须定义一个等价关系(它必须是自反的对称的和可传递的 )。 另外,它必须是一致的 (如果对象没有被修改,那么它必须保持返回相同的值)。 而且, o.equals(null)必须总是返回false。

hashCode() ( javadoc )也必须是一致的 (如果对象没有被修改为equals() ,它必须保持返回相同的值)。

这两种方法之间的关系是:

每当a.equals(b) ,那么a.hashCode()必须与b.hashCode()相同。

在实践中:

如果你重写一个,那么你应该重写另一个。

使用你用来计算equals()来计算hashCode()的同一组字段。

使用Apache Commons Lang库中优秀的帮助器类EqualsBuilder和HashCodeBuilder 。 一个例子:

 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(); } } 

另外请记住:

当使用基于散列的Collection或Map (如HashSet , LinkedHashSet , HashMap , Hashtable或WeakHashMap)时 ,请确保放入集合中的关键对象的hashCode()在对象位于集合中时永不改变。 确保这一点的防弹方法是使您的密钥不可变, 这也有其他好处 。

有些问题值得注意,如果你正在处理像Hibernate那样使用Object-Relationship Mapper(ORM)持久化的类,如果你不认为这已经非常复杂了!

延迟加载的对象是子类

如果你的对象使用ORM持久化,在很多情况下,你将会处理dynamic代理,以避免太早从数据存储加载对象。 这些代理被实现为你自己类的子类。 这意味着this.getClass() == o.getClass()将返回false 。 例如:

 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,那么使用o instanceof Person是唯一一个能够正确运行的东西。

延迟加载的对象具有空字段

ORM通常使用getters强制加载延迟加载的对象。 这意味着,即使person.getName()强制加载并返回“John Doe”,如果person是懒加载的, person.name将为null 。 根据我的经验,这在hashCode()equals()经常出现。

如果您正在处理ORM,请务必始终使用getter,并且不要在hashCode()equals()使用字段引用。

保存一个对象将改变它的状态

持久对象通常使用一个id字段来保存对象的关键字。 首次保存对象时,该字段将自动更新。 不要在hashCode()使用一个id字段。 但是你可以在equals()使用它。

我经常使用的模式是

 if (this.getId() == null) { return this == other; } else { return this.getId().equals(other.getId()); } 

但是:你不能在hashCode()包含getId() hashCode() 。 如果你这样做,当一个对象被持久化时,它的hashCode改变。 如果对象在HashSet ,那么你将永远不会再find它。

在我的Person例子,我可能会使用getName()hashCodegetId()getName() (只为偏执狂)的equals() 。 如果hashCode()存在一些“冲突”的风险,但对于equals()却不会有问题。

hashCode()应该使用equals()不变的属性子集

关于obj.getClass() != getClass()

这个语句是equals()inheritance不友好的结果。 JLS(Java语言规范)指定如果A.equals(B) == true那么B.equals(A)也必须返回true 。 如果你省略inheritance了equals()类的声明(并改变它的行为)将会破坏这个规范。

考虑下面的例子,当语句被省略时会发生什么:

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

new A(1).equals(new A(1))同样, new B(1,1).equals(new B(1,1))结果也是如此。

这看起来非常好,但看看如果我们尝试使用这两个类会发生什么:

 A a = new A(1); B b = new B(1,1); a.equals(b) == true; b.equals(a) == false; 

显然,这是错误的。

如果你想确保对称的条件。 a = b如果b = a且Liskovreplace原则不仅在B实例的情况下调用super.equals(other) ,而且在A实例之后检查:

 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; 

哪个会输出:

 a.equals(b) == true; b.equals(a) == true; 

如果a不是B的引用,那么它可能是A类的引用(因为你扩展了它),在这种情况下,你也可以调用super.equals()

为了inheritance友好的实现,请查看Tal Cohen的解决scheme, 我如何正确实现equals()方法?

概要:

Joshua Bloch在他的着作“ 有效Java编程语言指南” (Addison-Wesley,2001)中声称:“在保持等价契约的同时,根本无法扩展可实例化的类并添加一个方面。 塔尔不同意。

他的解决scheme是通过调用另一个非对称的blindlyEquals()来实现equals()。 blindlyEquals()被子类覆盖,equals()被inheritance,并且从不被覆盖。

例:

 class Point { private int x; private int y; protected boolean blindlyEquals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return (px == this.x && py == 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); } } 

请注意,如果要满足Liskovreplace原则 ,则equals()必须跨inheritance层次结构工作。

仍然感到惊讶的是,没有人推荐这个番石榴图书馆。

  //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。 我们需要将它们覆盖到自定义对象。

 public boolean equals(Object obj) public int hashCode() 

相等的对象必须产生相同的哈希码,只要它们相等,不平等的对象不需要产生明显的哈希码。

 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

玩的开心! @。@

在检查成员平等之前,有几种方法可以检查类的平等性,我认为这两种方法在正确的情况下都是有用的。

  1. 使用instanceof运算符。
  2. 使用this.getClass().equals(that.getClass())

我在final equals实现中使用了#1,或者实现了一个规定equals的algorithm的接口(比如java.util集合接口 – 使用(obj instanceof Set)或者你实现的任何接口进行检查的正确方法) 。 当等于可以被覆盖时,通常是一个不好的select,因为这打破了对称性。

选项#2允许类安全地扩展而不会超越等号或破坏对称性。

如果你的类也是ComparableequalscompareTo方法也应该是一致的。 以下是Comparable类中equals方法的模板:

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

对于平等,请看Angelika Langer 的“等于秘密” 。 我非常喜欢它。 她也是关于Java中generics的一个很好的常见问题解答。 在这里查看她的其他文章(向下滚动到“Core Java”),在那里她也继续Part-2和“混合types比较”。 玩得开心阅读!

equals()方法用于确定两个对象的相等性。

因为int值10总是等于10.但是这个equals()方法是关于两个对象的相等的。 当我们说对象,它会有属性。 决定平等的这些属性被考虑。 必须考虑到所有的财产,以确定平等和相对于类的定义和上下文可以决定。 然后equals()方法可以被覆盖。

当我们重写equals()方法时,我们应该总是重写hashCode()方法。 如果不是,会发生什么? 如果我们在应用程序中使用哈希表,它将不会像预期的那样工作。 由于hashCode用于确定所存储值的相等性,因此它不会返回某个键的正确对应值。

给出的默认实现是Object类中的hashCode()方法使用对象的内部地址并将其转换为整数并将其返回。

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

代码输出示例:

 bengalTiger1 and bengalTiger2: true bengalTiger1 and siberianTiger: false bengalTiger1 hashCode: 1398212510 bengalTiger2 hashCode: 1398212510 siberianTiger hashCode: –1227465966 

我发现一个问题是两个对象包含对方的引用(一个例子是一个父/子关系与父方便方法获取所有孩子)。
例如,在做Hibernate映射时,这些事情是相当普遍的。

如果在hashCode中包含关系的两端,或者等于testing,则可能进入以StackOverflowException结尾的recursion循环。
最简单的解决scheme是不在方法中包含getChildren集合。

逻辑上我们有:

a.getClass().equals(b.getClass()) && a.equals(b) a.hashCode() == b.hashCode()

但反之亦然!