1.hashcode是什么?
自我总结:hashcode实际上是对象的地址基础上通过哈希算法演变过来的一个值,用于表现hash表中的位置
这个值返回默认为int,这是由于Object父类其默认返回为int,而其它类都是基于这个类的,所以重写hashcode也默认返回int
其中hashcode的主要作用就在于通过hash表能快速取值,而这个hash表就是用于java的基于散列集合方面的,诸如Hashmap、HashSet的存取。对其它除散列集合外的(比如list、arrary)影响不大,只是一个规范说明而已。
2.为什么要覆盖hashcode?
这是Obejct的一种规范,相等的对象必须有相同的hashcode,不同的对象也可以有相同的hashcode(但最好hashcode也不同)。
之所以覆盖equals时总要覆盖hashCode是因为,使用Hashmap的时候它会进行判断的。
举个例子:使用hashmap.put(xx,"key").设置不能添加重复的对象。
存入2个对象时是否可以同时存在,就可以根据hashcode方法和equals方法。(之所以使用hashcode方法就是为了减少equals的使用,减少对内存地址比较“==“的直接使用,调用对象时可以直接通过hashcode来访问散列地址。减少了使用equals的使用,提高了效率。)
比如:hashcode返回的值一样同时equals返回true,则判断这两对象是相同,无法相同存在。
比如:两个对象是同一个,hashcode返回的值不一样,equals返回true,则判断这两对象是不同,就容易出错了。
也就是说hashcode方法和equals方法只要有一个不一样。HashMap会判断对象是不同的。
这也就引出一个规范原则:相等的对象必须具有相等的散列码(hash code);
3.如何覆盖hashcode?
解析自定义类中与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实现所采用的算法)
public class UserRole implements Serializable {
private Integer userId;
private Integer roleId;
public UserRole() {
}
public UserRole(Integer userId, Integer roleId) {
this.userId = userId;
this.roleId = roleId;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getRoleId() {
return roleId;
}
public void setRoleId(Integer roleId) {
this.roleId = roleId;
}
@Override
public String toString() {
return "UserRole{" +
"userId=" + userId +
", roleId=" + roleId +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRole userRole = (UserRole) o;
if (userId != null ? !userId.equals(userRole.userId) : userRole.userId != null) return false;
return roleId != null ? roleId.equals(userRole.roleId) : userRole.roleId == null;
}
@Override
public int hashCode() {
int result = userId != null ? userId.hashCode() : 0;
result = 31 * result + (roleId != null ? roleId.hashCode() : 0);
return result;
}
}
上面是一个用户角色表类的IDEA默认重写的实例,从中可以看出,它遵从了上面的几条原则,以获取第一个参数的hashcode为基础,再累加后面参数的hashcode。
之所以result乘以31是一个规范,合适的使用范围:
1.返回范围合适,不冲突也不超出范围:31是质子数中一个“不大不小”的存在,过于小比如2,3,5,那么哈希值容易相同,造成冲突。如果过大(100以上),比如101,那么得出的哈希值会超出int的最大范围
2.可以被 JVM 优化:
JVM里最有效的计算方式是位运算了:
* 左移 << : 左边的最高位丢弃,右边补全0(把 << 左边的数据*2的移动次幂)。
* 右移 >> : 把>>左边的数据/2的移动次幂。
* 无符号右移 >>> : 无论最高位是0还是1,左边补齐0。
所以 : 31 * i = (i << 5) - i(左边 31*2=62,右边 2*2^5-2=62) - 两边相等,JVM就可以高效的进行计算啦。。。