经常能看到重写equals方法就需要重写hashCode方法的说法,这点也很好理解,假如重写equals使得两个对象通过equals判断为真 ,但是如果hashCode计算出来的值如果不一样,就会发生矛盾,就是明明两个对象是一样的,但是却会被映射到不同位置,这样子的话,hashMap或者hashSet之类的哈希结构就会存储多个相同的对象。还可以通过一个例子理解
Map<String,Value> map1 = new HashMap<String,Value>();
String s1 = new String("key");
String s2 = new String("key");
Value value = new Value(2);
map1.put(s1, value);
System.out.println("s1.equals(s2):"+s1.equals(s2));
System.out.println("map1.get(s1):"+map1.get(s1));
System.out.println("map1.get(s2):"+map1.get(s2));
Map<Key,Value> map2 = new HashMap<Key,Value>();
Key k1 = new Key("A");
Key k2 = new Key("A");
map2.put(k1, value);
System.out.println("k1.equals(k2):"+k1.equals(k2));
System.out.println("map2.get(k1):"+map2.get(k1));
System.out.println("map2.get(k2):"+map2.get(k2));
Key和Value的类定义如下
static class Key{
private String k;
public Key(String key){
this.k=key;
}
//如果不重写hashCode,只重写了equals,会造成相同值被放入不同的桶中
// @Override
// public int hashCode() {
// return k.hashCode();
// }
@Override
public boolean equals(Object obj) {
if(obj instanceof Key){
Key key=(Key)obj;
return k.equals(key.k);
}
return false;
}
}
static class Value{
private int v;
public Value(int v){
this.v=v;
}
@Override
public String toString() {
return "类Value的值-->"+v;
}
}
输出结果如下
可以看出,如果重写了equals但不重写hashCode的话,会出现相同的对象会被map判断成不同对象,导致可以重复插入多个相同对象。
除此之外,还会思考如果重写hashCode但不重写equals方法的情况下,又会造成什么问题,因此用以下例子说明
Map<Integer, Integer> map3 =new HashMap();
while (true){
boolean flag = false;
for (int i = 0; i < 1000; i++) {
if(!map3.containsKey(i)){
map3.put(i, i);
flag = true;
}
}
if (flag == false) {
break;
}
System.out.println("map3的容量" + map3.size());
}
Map<Key2, Integer> map4 =new HashMap();
while (true){
boolean flag = false;
for (int i = 0; i < 1000; i++) {
if(!map4.containsKey(new Key2(i))){
map4.put(new Key2(i), i);
flag = true;
}
}
if (flag == false) {
break;
}
System.out.println("map4的容量" + map4.size());
}
Key2的类定义如下
static class Key2{
Integer id;
Key2(Integer id) {
this.id = id;
}
@Override
public int hashCode() {
return id.hashCode();
}
//不重写equals就会导致一直认为没有相同的值,就会一直插入。
// @Override
// public boolean equals(Object obj) {
// if(obj instanceof Key2){
// Key2 key2 =(Key2)obj;
// return id.equals(key2.id);
// }
// return false;
// }
}
结果如下
从图中结果可以看出,map4一直在添加数据,说明map一直认为没有相同的key对象,因此对于同一个i,不重写的equals永远不会判断相同,所以会一直插入。因此hashCode和equals必须全部重写,任何一个不重写都会发生错误。
到这里也还会思考,String和Integer的和hashCode和equals是怎么计算的
Integer的hashCode计算如下
可以看出是直接返回原始值
String的hashCode计算如下
可以由注释看出来,计算的结果就是s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1],就比如字符串“abc”,a的ascll码是97,b是98,c是99,因此该字符串的hashCode值就是(97 *31 + 98)*31 + 99,这里引出一点思考:为什么用31呢?,查阅资料得知因为31是一个质数,可以使得减少哈希算法的冲突概率,同时31的二进制数是11111,因此31 *i就等于(i << 5) - i,可以优化运算。
Integer的equals计算如下
就是说明,只需要使用instanceof判断传入对象是否是Integer的实例或者子类,是就强转成Integer类,然后判断值是否相等
String的equals计算如下
这里首先用“==”比较了equals两边对象,如果一样直接返回true,然后就是用instanceof判断是否是String实例或者子类,如果是就强转,然后再根据数组长度判断是否相同,如果相同就遍历数组每个元素,都相同就返回true,其他情况都返回false。