为什么重写equals()一定要重写hashCode()呢

为什么重写equals()一定要重写hashCode()呢?

前言

本文介绍java.lang.Object类中的两个方法:equals和hashCode。这两个方法大家应该都知道,但是这两个方法的作用是什么、为什么重写equals还要重写hashCode、它们之间有什么关系和约定等,或许有些小伙伴还不是很清楚,知其然知其所以然,今天就来带大家了解一下。

准备User类

public class User {
    private String name;
    private Integer age;
    private String address;
}

1.equals()与==的区别

  • ==在基本数据类型中是直接比较值的(例如 2 == 2,结果为true),但是在比较对象的时候是按照地址值去判断的,也就是说就算两个对象的变量值都相等但 == 还是认为两个对象不等。

  • 在没有重写equals()时可以默认和==的作用一样,也是去比较地址的。

  • String和Integer默认已经重写了equals()了。

    User user1 = new User("张三", 18, "武汉");
    User user2 = new User("张三", 18, "武汉");
    
    System.out.println(user1 == user2); //false
    System.out.println(user1.equals(user2)); //没有重写equals false
    

2.为什么要重写equals()?

  • 当我们需要用两个对象的值来判断是否相等时就需要重写equals()。

  • 重写equals()方法的例子如下:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) 
            	&& Objects.equals(age, user.age) 
           	 	&& Objects.equals(address, user.address);
    }
    

3.hashCode()是什么?

hash 翻译做“散列”,也可直译为“哈希”,就是把任意长度的值输入,通过hash()函数输出固定长度的消息摘要。 hash函数也有很多种,包括:直接取余法,乘法取整法,平方取中法,暂时先了解有这些方法即可。

  • 简单的来说hashCode就是一个对象的编码,可以利用这个编码快速查询到对象。

  • java中HashMap,HashSet等都用到了hash结构来提高查询速率。

  • 哈希桶的大小最好是2的n次方,因为这样可以用高效的&运算代替%运算,计算hash值

    X % 2^n = X & (2^n – 1)

    假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 = 7 ,即0111。

    此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。

    从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。

    6 % 8 = 6 ,6 & 7 = 6

    10 & 8 = 2 ,10 & 7 = 2

  • 默认的hashCode是java通过对象地址进行计算的,当我们想要让值相同的对象hashCode也相同就需要重写hashCode()覆盖默认的方法。

  • 重写hashCode()的例子如下:

    @Override
    public int hashCode() {
        return Objects.hash(name, age, address);
    }
    

4.为什么重写equals()方法就要重写hashCode()?

在重写hashCode()时,应该遵循JavaSE的官方指导:

如果2个对象使用equals()对比的结果为true,则这2个对象的hashCode()返回的结果应该相同。

如果2个对象使用eauals()对比的结果为false,则这2个对象的hashCode()返回的结果应该不同。

  • 当我们两个方法都不重写时是用地址来判断,显然符合要求。

  • 在我们对HashMap进行put时会先进行hashCode比较,如果没有相等的就直接放进哈希桶中,相同则会调用对象的equals方法,不相等就直接放进哈希桶中(例如用链表连接解决hsah冲突问题),相等则认为是同一个对象则不会进行存储。

  • 当我们只重写equals(),而不重写hashCode()就会出现,两个对象equals为true但是hash值却会不相等,这样就会导致我们根据两个完全一样(这里是指值相同的两个对象)的对象去HashMap中查询为null的现象,并且会导致相同对象多次存储浪费空间,而且还会出现很多不可预知的错误。

  • 之所以有规定,就是为了使诸如HashMap这样的哈希表正常使用。

  • 测试只重写equals()没有重写hashCode():

    @org.junit.jupiter.api.Test
    public void testUser(){
        User user1 = new User("张三", 18, "武汉");
        User user2 = new User("张三", 18, "武汉");
        User user3 = new User("张三", 18, "武汉");
    
        HashMap<User, Integer> map = new HashMap<>();
        map.put(user1, 1);
        map.put(user2, 2);
    
        System.out.println(map.size()); // 2
        System.out.println(map.get(user1)); // 1
        System.out.println(map.get(user2)); // 2
        System.out.println(map.get(user3)); // null
    }
    
  • 测试重写equals()和重写hashCode():

    @org.junit.jupiter.api.Test
    public void testUser(){
        User user1 = new User("张三", 18, "武汉");
        User user2 = new User("张三", 18, "武汉");
        User user3 = new User("张三", 18, "武汉");
    
        HashMap<User, Integer> map = new HashMap<>();
        map.put(user1, 1);
        map.put(user2, 2);
    
        System.out.println(map.size()); // 1
        System.out.println(map.get(user1)); // 2
        System.out.println(map.get(user2)); // 2
        System.out.println(map.get(user3)); // 2
    }
    

总结

  • 总的来说重写equals不重写hsahcode会导致equal相等而hashCode不相等的情况

  • 阿里就针对这个,强制要求规范编码!!

阿里巴巴开发规范

【强制】关于 hashCode 和 equals 的处理,遵循如下规则:

1) 只要重写 equals,就必须重写 hashCode。

2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。

3) 如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。
说明:String 因为重写了 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用。

最后

  • 我们可以用(Lombok 常用注解)插件中的@Data注解自动生成equals和hashCode方法等,简洁生成JavaBean,简化了很多代码

  • 例如下:

    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class User {
        private String name;
        private Integer age;
        private String address;
    }
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值