为什么重写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; }