??要彻底了解透为什么重写equals()方法同时要重写hashcode()方法,我们不妨先来看看这样一个例子:
String a=new String("123");
String b=new String("123");
System.out.println(a==b);
System.out.println(a.equals(b));
System.out.println(a.hashCode()==b.hashCode());
??在以上例子中,我们创建了两个字符串对象,虽然两个字符串对象的内容是相同的,都是“123”,但显然这是两个不同的对象,符号“
=
=
==
==”比较的是对象的在内存中的物理地址,既然a、b是两个不同的对象,那么他们对象的物理地址肯定不同,所以表达式“a
=
=
==
==b”结果是false。众所周知,equals()方法的作用本来和==相同,也是比较两者的物理地址,这一点可以在Object类中equals()方法的实现中可以证明:
public boolean equals(Object obj) {
return (this == obj);
}
但是在String类中重写了equals()方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
于是String类的equals方法变成了比较两个字符串对象的字符串内容是否相等,所以表达式“a.equals(b)”结果是true,那么为什么表达式“a.hashCode()==b.hashCode()”的结果也是true呢,是因为hashCode()方法在String类中也进行了重写。 ??我们可以比较一下hashCode()方法在Object类中的实现以及在String类重写之后的实现。
这是hashCode()在Object类中的实现:
@HotSpotIntrinsicCandidate
public native int hashCode();
??从声明关键字native可以看出这是一个本地方法,具体是用更加底层的C语言实现的,大概阅读native方法的注释说明后可以了解到hashCode()方法返回的是一个数字,这个数字对于同一个对象来说具有唯一性,与这个对象的引用无关,只与这个对象的内存物理地址有关
我们再来看看hashCode()方法在String类中的实现:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
hash = h = isLatin1() ? StringLatin1.hashCode(value)
: StringUTF16.hashCode(value);
}
return h;
}
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
}
??从实现方式中我们也可以看出,hashCode()也和equals()方法从与对象物理内存地址相关变成了与对象字符串的内容相关。
??也就是说,如果通过equals()方法判断两个对象是否相等的结果要与两个对象hashCode()方法返回的整数进行比较得出的结果保持一致性,那么为什么要这样做呢? ??首先,我们要明白hashCode()方法的作用是确定对象在散列存储结构例如HashMap、HashSet中的存储地址(注:这个地址不是对象在内存中的真实存储地址,只是在数据结构中的逻辑地址)。 ??此刻我们就能回答为什么重写equals()方法同时要重写hashcode()方法了:如果重写了equals()方法,没有重写hashCode()方法的话,如果a.equals(b),但a的hashCode与b的hashCode不相等,则当我们将a、b同时加入散列存储结构map、set时,就可能出现数据结构中存在两个值相等的对象的情况,从而导致混淆。 ??所以我们在设计Java类对象要遵循以下原则:
- 如果两个对象通过equals()方法比较相等,那么这两个对象的hashCode一定相同。
- 如果两个对象hashCode相同,不能证明两个对象是同一个对象,只能证明两个对象在散列结构中存储在同一个地址(不同对象hashCode相同的情况称为hash冲突)。
最后我们来看看遵守以上原则的String类在散列存储结构中的存储情况:
String a=new String("123");
String b=new String("123");
System.out.println(a==b);
System.out.println(a.equals(b));
System.out.println(a.hashCode()==b.hashCode());
Set<String>set=new HashSet<>();
set.add(a);
System.out.println(set.contains(b));
??以上例子可以看出,虽然a、b不是同一个对象,但是由于表达式“a.equals(b)”结果是true,所以a的hashCode与b的hashCode相等,两者在set中存储地址相同,在set中加入a后,在set中查询b,返回结果是true。 ??综上,重写equals()方法同时尽量也要重写hashcode()方法。
|