为什么重写equals一定要重写hashcode
✈️

为什么重写equals一定要重写hashcode

Created
May 1, 2024 08:33 AM
Tags
在原始的 Object 类中
默认 equals 比较规则是通过“==”来比较两个对象的内存地址
默认的 hashCode 方法返回的是对象的内存地址由哈希算法转换成的一个整数
哈希算法具有一定的偶然性,不同的内存地址可能计算出相同的哈希值。
对于原始的 hashCode( ) 和equals( ) 方法来说,通过 equals( ) 比较两个对象相等,说明这两个对象的内存地址相同,进而知 hashcode 也是相同的。
 
在实际使用中,大多数场景下,如 HashMap 中存放自定义对象类作为 key
当用 HashMap 存入自定义的类时,如果不重写这个自定义类的 equals 和 hashCode 方法,得到的结果会和预期的不一样。
public class HashMapKey { private Integer id; public HashMapKey(Integer id) { this.id = id; } public Integer getId() { return id; } }
public class TestHashMap { public static void main(String[] args) { HashMapKey k1 = new HashMapKey(1); HashMapKey k2 = new HashMapKey(1); HashMap<HashMapKey, String> map = new HashMap<>(); map.put(k1, "Cuctut"); System.out.println("map.get(k2) : " + map.get(k2)); } }
它的执行结是:map.get(k2) : null。出现这个情况的原因有两个:
  • 没有重写 hashCode 方法
  • 没有重写 equals 方法。
当往 HashMap 里放 k1 时,首先会调用 HashMapKey 这个类的 hashCode 方法计算它的 hash 值,随后把 k1 放入 hash 值所指引的内存位置
但是在 HashMapKey 中没有重写 hashCode 方法,所以这里调用的是顶级父类 Object 类的 hashCode 方法,而 Object 类的 hashCode 方法返回的 hash 值其实是 k1 对象的内存地址得到的 hashcode 。
通过map.get(k2) 查询 map 时,还是会调用 Object 类的 hashCode 方法计算 k2 的 hash值,得到的是 k2 的内存地址得到的 hashcode。由于 k1 和 k2 是new出来的两个不同的对象,具有不同的内存地址空间,也就是说它们的 hash 值一定不同。所以通过 k2 是无法得到 k1
当重写 hashCode 方法后
@Override public int hashCode() { return id.hashCode(); }
此时因为 hashCode 方法返回的是 id 的 hash值,所以此处 k1 和 k2 这两个对象的 hash 值就变得相等了。
存 k1 时,是根据它 id 的 hash 值,假设这里是 103,把 k1 对象放入到对应的位置。而通过 k2 取时,是先计算它的 hash 值,由于 k2 的 id 也是 1,这个值也是 103,随后到这个位置去找。按道理应该可以找到。
但运行结果还是会出乎意料:map.get(k2) : null
HashMap 是用链地址法来处理冲突,也就是说,在 103号位置上,有可能存在着多个用链表形式存储的对象。它们通过 hashCode 方法返回的 hash 值都是 103。
当通过 k2 的 hashCode 到 103号位置查找时,确实会得到 k1。但 k1 有可能仅仅是和 k2 具有相同的 hash值,但未必和 k2 相等。
判断完hashcode相同后,这个时候就需要调用 HashMapKey 对象的 equals 方法来判断两者是否相等了。
由于在 HashMapKey 对象里没有定义 equals 方法,系统就不得不调用 Object 类的 equals 方法,由于 Object 的原生equals方法是根据两个对象的内存地址来判断,而k1和k2是new出来的两个对象具有不同的内存空间,所以 k1 和 k2 一定不会相等,这就是为什么依然得到 null 的原因。
重写 equals 方法后即可得到需要的结果
@Override public boolean equals(Object o) { if (o == null || !(o instanceof HashMapKey)) { return false; } else { return this.getId().equals(((HashMapKey) o).getId()); } }
如果只重写了equals不重写hashcode呢?
定义一个 Student 类只重写了Object 的 equals 方法,没有重写 hashCode 方法
@Getter @Setter @NoArgsConstructor @AllArgsConstructor public class student { private String id; private String name; @Override public boolean equals(Object o) { if(this == o) return true; if(o == null || getClass() != o.getClass()) return false; student student = (student) o; return Object.equals(id, student.id) && Object.equals(name, student.name); } }
@Test public void test01() { student s1 = new student("1", "cuctut"); student s2 = new student("1", "cuctut"); System.out.println(s1.equals(s2)); //true System.out.println(s1.hashCode() == s2.hashCode()); //false }
如果我们把新建出来的两个Student对象放入HashSet集合中:
@Test public void test01() { student s1 = new student("1", "cuctut"); student s2 = new student("1", "cuctut"); HashSet<student> set = new HashSet<>(); set.add(s1); set.add(s2); System.out.println(set.size()); //2 }
是因为HashSet的底层其实就是HashMap,当存放对象时,先调用这个对象的 hashCode 方法计算存放位置。由于 student 没有重写 hashCode 方法,所以使用的是 Object 类的 hashCode 方法,所以存放的位置在底层数组上是不一样的,不会触发HashSet的去重功能,而对于程序员来说,两个相同的对象却会在HashSet中出现多次。
根据面向对象思想,只要值相同,就为相同两个对象,重写 hashCode 方法即可解决
@Override public int hashCode() { return Object.hash(id, name); }
感谢