此前一位同事问我为何需要重写hashCode(),而且为什么说重写equals()同时也应该hashCode(),下面我就对hashCode()及equals()作一下介绍。
一。相等性 equals方法
Object的equals()方法内容如下:
public
boolean
equals(Object obj)
...
{ return (this == obj); } 可以看出默认的相等性是比较内存位置是否相等。但对于自定义类,我们一般都会重写equals()方法,以实现语义相等性。例如,对于类Student
public
class
Student
...
{
private String number;
private String name;
//filed setter/getter method
}
我们的equals()方法如下:
public boolean
equals(Object object)
...
{
if (object == null) ...{
return false;
}
if(!(object instanceof Student)) ...{
return false;
}
Student that = (Student)object;
return this.number.equals(that.number) && this.name.equals(that.name);
}
从以上重写 equals()方法应该理解到对象内容的比较才是设计equals()的真正目的。
并且Java语言对equals()的要求如下,这些要求是必须遵循的。
- 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
- 反射性:x.equals(x)必须返回是“true”。
- 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
- 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
- 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。
二。集合与相等性 hasCode()方法
看看以下代码:
Student A = new
Student(
"
001
"
,
"
yangqiang
"
);
Student sameA = new
Student(
"
001
"
,
"
yangqiang
"
);
Map < Student, String >
map
=
new
HashMap
<
Student, String
>
();
map.put(A, "" );
boolean isConKey =
map.containKey(sameA);
乍一看,isConKey 应该为true,其实不然,原因出在哪里呢?
下面就来分析分析, HashMap基于哈希表结构进行存储和获取数据。而现在的hashCode()方法是默认的Object中的HashCode方法返回的HashCode对应于当前 的地址,也就是说对于不同的对象,即使它们的内容完全相同,用HashCode()返回的值也会不同。这样实际上违背了我们的意图。所以返回的 isConKey 应该为false,这下应该就清楚原因了吧,哈哈。
知道原因之后,我们回到我们在使用 HashMap时, 希望利用相同内容的对象索引得到相同的目标对象的问题上,这就需要我们重写HashCode()方法。
在重写HashCode()方法之前,我们需要知道:
(1)Object中对hashCode的约定:
- 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
- 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
- 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。
(2)hashCode()的返回值和equals()的关系如下:
1.如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
2.如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。
(3)Hash冲突。
常见的Hash冲突是不同key对象最终产生了相同的索引,而一种非常甚至绝对少见的Hash冲突是,如果一组对象的个数大过了int范围,而 HashCode的长度只能在int范围中,所以肯定要有同一组的元素有相同的HashCode,这样无论如何他们都会有相同的索引.当然这种极端的情况 是极少见的,可以暂不考虑,但是对于同的HashCode经过取模,则会产中相同的索引,或者不同的对象却具有相同的HashCode,当然具有相同的索 引。
基于以上,一个理想的哈希算法应该是自定义类属性域的算术组合,并且哈希值运算使用质数,因为对哈希长度进行求余运算时可以得到较好的分布,减少哈希冲突。以下为示例:
public int hashCode() ...
{
int hash = 1; //任意质数
hash = hash * 31 + someNonNullField.hashCode();
hash = hash * 31
+ (someOtherField == null ? 0 : someOtherField.hashCode());
return hash;
}
应用上面的算法重写Student的hashCode()方法,重新运行 boolean isConKey = map.containKey(sameA);
isConKey 返回true,哈哈。