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; } @Override public boolean equals(Object obj) { if(this==obj) return true; if(obj instanceof Student){ if(this.getStuNum()==((Student)obj).getStuNum()) return true; } return false; } }
输出为:
123 --- Lily 456 --- Jerry 123 --- 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; } @Override public boolean equals(Object obj) { if(this==obj) return true; if(obj instanceof Student){ if(this.getStuNum()==((Student)obj).getStuNum()) return true; } return false; } @Override public int hashCode() { return getStuNum(); } }
输出:
456 --- Jerry 123 --- Tom
重写了 hashcode 方法返回学号。OK,大功告成。