Answers:
equals()
(javadoc)必须定义一个等价关系(它必须是自反的,对称的和可传递的)。此外,它必须是一致的(如果未修改对象,则它必须保持返回相同的值)。此外,o.equals(null)
必须始终返回false。
hashCode()
(javadoc)也必须是一致的(如果未根据修改对象equals()
,则它必须保持返回相同的值)。
该关系的两种方法之间是:
每当
a.equals(b)
,则a.hashCode()
必须与相同b.hashCode()
。
如果覆盖一个,则应覆盖另一个。
使用用于计算的相同字段集equals()
进行计算hashCode()
。
使用优秀的辅助类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()永远不会改变。确保这一点的防弹方法是使您的钥匙不可变,这还有其他好处。
instanceof
操作数的第一个操作数为null则返回false(再次有效Java),因此不必进行第一个空检查。
如果您正在处理使用像Hibernate这样的使用对象关系映射器(ORM)持久化的类,则有一些值得注意的问题,如果您不认为这已经变得不合理地复杂的话!
延迟加载的对象是子类
如果您的对象是使用ORM持久保存的,那么在很多情况下,您将使用动态代理来避免从数据存储中加载对象太早。这些代理被实现为您自己类的子类。这意味着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通常使用吸气剂来强制加载延迟加载的对象。这意味着,person.name
将是null
,如果person
是懒加载,即使person.getName()
迫使装载并返回“李四”。以我的经验,这种现象在hashCode()
和出现的频率更高equals()
。
如果要处理ORM,请确保始终使用getter,并且切勿在hashCode()
和中使用字段引用equals()
。
保存对象将更改其状态
持久对象通常使用id
字段来保存对象的键。首次保存对象时,此字段将自动更新。请勿在中使用id字段hashCode()
。但是您可以在中使用它equals()
。
我经常使用的模式是
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
但是:你可以不包括getId()
在hashCode()
。如果这样做,则持久化对象后,对象将hashCode
发生更改。如果对象位于中HashSet
,您将“永远”不再找到它。
在我的Person
例子,我可能会使用getName()
的hashCode
,并getId()
加getName()
(只是偏执)的equals()
。如果存在“冲突”的风险是可以的hashCode()
,但绝对不可以equals()
。
hashCode()
应该使用以下属性的不变子集 equals()
Saving an object will change it's state
!hashCode
必须返回int
,那么您将如何使用getName()
?您能举个例子吗hashCode
有关的澄清obj.getClass() != getClass()
。
该语句是equals()
继承不友好的结果。JLS(Java语言规范)指定了if,A.equals(B) == true
则B.equals(A)
还必须返回true
。如果省略该语句,则继承的类将覆盖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;
显然,这是错误的。
如果要确保对称条件。如果b = a,则a = b,并且Liskov替换原理super.equals(other)
不仅在B
实例的情况下调用,而且在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()
过。
ThingWithOptionSetA
可以等于a Thing
,并且所有附加选项都具有默认值,并且同样对a而言ThingWithOptionSetB
,那么只有两个对象的所有非基本属性都与它们的默认值匹配时,才ThingWithOptionSetA
可以将a等于a进行比较。ThingWithOptionSetB
我看不出您如何对此进行测试。
B b2 = new B(1,99)
,然后b.equals(a) == true
和a.equals(b2) == true
但是b.equals(b2) == false
。
对于继承友好的实现,请查看Tal Cohen的解决方案,如何正确实现equals()方法?
摘要:
约书亚·布洛赫(Joshua Bloch)在他的《有效的Java编程语言指南》(艾迪生-韦斯利,2001年)中宣称:“根本没有办法扩展可实例化的类并在保留平等契约的同时增加方面。” 塔尔不同意。
他的解决方案是通过两种方式调用另一个非对称的blindEquals()来实现equals()。子类覆盖blindlyEquals(),equals()被继承,并且从不覆盖。
例:
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()必须跨继承层次结构工作。
if (this.getClass() != o.getClass()) return false
,但很灵活,它仅在派生类麻烦修改等于时才返回false。那正确吗?
仍然感到惊讶的是,没有人为此建议使用番石榴库。
//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());
}
this
参与this.getDate()
毫无意义(除了混乱)
if (!(otherObject instanceof DateAndPattern)) {
。与Hernan和Steve Kuo达成协议(尽管这是个人喜好问题),但还是+1。
在超类中有两种方法,分别是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
玩得开心!@。@
在检查成员相等性之前,有两种方法可以检查类是否相等,我认为这两种方法在正确的情况下都是有用的。
instanceof
运算符。this.getClass().equals(that.getClass())
。我在final
equals实现中使用#1 ,或者在实现为equals规定算法的接口时使用#1 (例如java.util
collection接口,这是使用with (obj instanceof Set)
或正在实现的任何接口进行检查的正确方法)。当可以覆盖equals时,这通常是一个错误的选择,因为这会破坏对称性。
选项#2允许安全地扩展该类,而不会覆盖等号或破坏对称性。
如果您的类也是Comparable
,则equals
和compareTo
方法也应保持一致。这是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;
}
}
final
,并且compareTo()
重写了该方法以颠倒排序顺序,则不应将子类和超类的实例视为相等。当这些对象在树中一起使用时,instanceof
可能找不到根据实现是“相等”的键。
对于平等,请看安吉莉卡·兰格(Angelika Langer)的《平等的秘密》。我非常爱它。她还是有关Java泛型的很好的常见问题解答。在这里查看她的其他文章(向下滚动到“ Core Java”),她还将继续进行第2部分和“混合类型比较”。祝他们阅读愉快!
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或equals测试中包含关系的两端,则有可能进入以StackOverflowException结尾的递归循环。
最简单的解决方案是在方法中不包括getChildren集合。
equals()
。如果一个疯狂的科学家创造了我的副本,我们将是等效的。但是我们不会有同一个父亲。