学Java的时候知道有时候要重写hashCode()和equals()方法,但是从来没写过,程序也没有因为这两个方法有过bug,hashCode()更是基本没用过。
昨天看到一个面试问题: 有没有重写过hashCode()。 没有。 那有没有在HashMap的key中放过自定义对象。 放过。 没重写hashCode()怎么在HashMap中放自定义对象。
HashMap中的Hash算法
在数据结构中如果寻找List中的某个值就要从头遍历,平均查找n / 2次,但是在Hash表中使用键值对存储所以只需要查找一次。
Hash表存储的过程有两个步骤: 1.使用哈希函数将key值转换成数组索引,在取值的时候再把要取的key转换成数组索引取出,但是有可能不同的值被转换成相同的数组索引,就会导致value值冲突,所以需要处理哈希碰撞。 2.处理哈希碰撞有:开放地址法,线性探索法,链地址法。
Java中的HashMap使用的是链地址法。
为什么要重写hashCode()
在HashMap中key值存放自定义对象来测试一下。(MyClass中只有一个Integer属性a)
代码语言:javascript复制Map<MyClass, Integer> map = new HashMap<>();
MyClass myClass1 = new MyClass(5);
MyClass myClass2 = new MyClass(5);
map.put(myClass1, 1);
System.out.println(map.get(myClass2));
结果为:null
打印一下myClass1和myClass2的hashCode():
代码语言:javascript复制System.out.println(myClass1.hashCode());
System.out.println(myClass2.hashCode());
结果为: 1581781576 1725154839
可以看到当没有重写hashCode()时两个对象的hashCode是不一样的,因为HashMap是根据key值的hashCode取值的两个key的hashCode不一样所以取不到相应的值。
那么重写一下MyClass中的hashCode():
代码语言:javascript复制@Override
public int hashCode() {
return this.a.hashCode();
}
因为MyClass中只有一个a属性,所以返回它的哈希值就可以了。 此时在打印一下两个对象的哈希值,都为5。
我们再来运行一下刚才的代码,结果为:null
这是因为没有重写equals()方法
为什么要重写equals()
HashMap是通过链地址法解决哈希冲突,在5这个位置上存在着myClass1和myClass2两个对象,这只能说明他们两个的哈希值相同但是不能说明他们两个就是相同的,这时就要调用equals()方法,由于我们没有重写equals()方法,所以会调用Object的equals()方法,Object的equals()方法是根据两个对象的地址来判断是否相等,两个对象在堆区的两个地方当然地址不会相同,所以我们要重写equals()来让他们两个相同:
代码语言:javascript复制@Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof MyClass)) {
return false;
} else {
return this.getA().equals(((MyClass) obj).getA());
}
}
重写equals()后再运行刚才的代码
当在HashMap中的key存放的是自定义对象时一定要重写hashCode()和equals()方法
如何重写hashCode()
如果类中只有一个属性直接返回即可,但是如果有多个属性就要用到别的方法: 第一步:定义一个初始值,一般来说取17
int result = 17;
第二步:分别解析自定义类中与equals方法相关的字段(假如hashCode中考虑的字段在equals方法中没有考虑,则两个equals的对象就很可能具有不同的hashCode)
情况一:字段a类型为boolean 则[hashCode] = a ? 1 : 0;
情况二:字段b类型为byte/short/int/char, 则[hashCode] = (int)b;
情况三:字段c类型为long, 则[hashCode] = (int) (c ^ c>>>32);
情况四:字段d类型为float, 则[hashCode] = d.hashCode()(内部调用的是Float.hashCode(d), 而该静态方法内部调用的另一个静态方法是Float.floatToIntBits(d))
情况五:字段e类型为double, 则[hashCode] = e.hashCode()(内部调用的是Double.hashCode(e), 而该静态方法内部调用的另一个静态方法是Double.doubleToLongBits(e),得到一个long类型的值之后,跟情况三进行类似的操作,得到一个int类型的值)
情况六:引用类型,若为null则hashCode为0,否则递归调用该引用类型的hashCode方法。
情况七:数组类型。(要获取数组类型的hashCode,可采用如下方法:s[0]31 ^ (n-1) s[1] 31 ^ (n-2) ..... s[n-1], 该方法正是String类的hashCode实现所采用的算法)
第三步:对于涉及到的各个字段,采用第二步中的方式,将其依次应用于下式:
result = result * 31 [hashCode];