Object 类是所有类的父类,其 equals 方法比较的是两个对象的引用指向的地址,hashcode 是一个本地方法,返回的是对象地址值。他们都是通过比较地址来比较对象是否相等的。其实这两个方法本身并没有任何关联。
为何重写 equals方法的同时必须重写 hashcode方法
可以这样理解:重写了 equals 方法,判断对象相等的业务逻辑就变了,类的设计者不希望通过比较内存地址来比较两个对象是否相等,而 hashcode 方法继续按照地址去比较也没有什么意义了,索性就跟着一起变吧。还有一个原因来源于集合。下面慢慢说举个例子:在学校中,是通过学号来判断是不是这个人的。下面代码中情景为学籍录入,学号 123 被指定给学生 Tom,学号 456 被指定给学生 Jerry,学号 123 被失误指定给 Lily。而在录入学籍的过程中是不应该出现学号一样的情况的。根据情景需求是不能添加重复的对象,可以通过 HashSet 实现。public class Test {public static void main(String[] args) {Student stu = new Student(123,"Tom");HashSet<Student> set = new HashSet<>();set.add(stu);set.add(new Student(456, "Jerry"));set.add(new Student(123, "Lily"));Iterator<Student> iterator = set.iterator();while (iterator.hasNext()) {Student student = iterator.next(); System.out.println(student.getStuNum() + " --- " + student.getName());}}};class Student {private int stuNum;private String name;public Student(int stuNum,String name){this.stuNum = stuNum;this.name = name;}public int getStuNum() {return stuNum;}public String getName() {return name;}@Overridepublic boolean equals(Object obj) {if(this==obj)return true;if(obj instanceof Student){if(this.getStuNum()==((Student)obj).getStuNum())return true;}return false;}}输出为:123 --- Lily456 --- Jerry123 --- Tom根据输出我们发现,再次将学号 123 指定给 Lily 居然成功了。到底哪里出了问题呢?我们看一下 HashSet 的 add 方法:public boolean add(E e) {return map.put(e, PRESENT)==null;}其实 HashSet 是通过 HashMap 实现的,由此我们追踪到 HashMap 的 put 方法:public V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}if (key == null)return putForNullKey(value);int hash = hash(key);int i = indexFor(hash, table.length);for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(hash, key, value, i);return null;}
- 根据 key,也就是 HashSet 所要添加的对象,得到 hashcode,由 hashcode 做特定位运算得到 hash 码;
- 利用 hash 码定位找到数组下标,得到链表的链首;
- 遍历链表寻找有没有相同的 key,判断依据是 e.hash == hash && ((k = e.key) == key || key.equals(k))。在add Lily 的时候,由于重写了 equals 方法,遍历到 Tom 的时候第二个条件应该是 true;但是因为 hashcode 方法还是使用父类的,故而 Tom 和 Lily的 hashcode 不同也就是 hash 码不同,第一个条件为 false。这里得到两个对象是不同的所以 HashSet 添加 Lily 成功。
总结出来原因是没有重写 hashcode 方法,下面改造一下:public class Test {public static void main(String[] args) {Student stu = new Student(123,"Tom");HashSet<Student> set = new HashSet<>();set.add(stu);set.add(new Student(456, "Jerry"));set.add(new Student(123, "Lily"));Iterator<Student> iterator = set.iterator();while (iterator.hasNext()) {Student student = iterator.next(); System.out.println(student.getStuNum() + " --- " + student.getName());}}};class Student {private int stuNum;private String name;public Student(int stuNum,String name){this.stuNum = stuNum;this.name = name;}public int getStuNum() {return stuNum;}public String getName() {return name;}@Overridepublic boolean equals(Object obj) {if(this==obj)return true;if(obj instanceof Student){if(this.getStuNum()==((Student)obj).getStuNum())return true;}return false;}@Overridepublic int hashCode() {return getStuNum();}}输出:456 --- Jerry123 --- Tom重写了 hashcode 方法返回学号。OK,大功告成。有人可能会奇怪,e.hash == hash && ((k = e.key) == key || key.equals(k)) 这个条件是不是有点复杂了,我感觉只使用 equals 方法就可以了啊,为什么要多此一举去判断 hashcode 呢?因为在 HashMap 的链表结构中遍历判断的时候,特定情况下重写的 equals 方法比较对象是否相等的业务逻辑比较复杂,循环下来更是影响查找效率。所以这里把 hashcode 的判断放在前面,只要 hashcode 不相等就玩儿完,不用再去调用复杂的 equals 了。很多程度地提升 HashMap 的使用效率。
所以重写 hashcode 方法是为了让我们能够正常使用 HashMap 等集合类,因为 HashMap 判断对象是否相等既要比较 hashcode 又要使用 equals 比较。而这样的实现是为了提高 HashMap 的效率。本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-09/135247.htm