EffectiveJava学习笔记6:覆盖equals时总要覆盖hashCode

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就可以高效的进行计算啦。。。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值