我們都知道Java語言是完全面向對象的,在java中,所有的對像都是繼承於Object類。
其equals 方法比較的是兩個對象的引用指向的地址,hashcode 是一個本地方法,返回的是對像地址值。 Ojbect類中有兩個方法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 --- 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;}1.根據key,也就是HashSet 所要添加的對象,得到hashcode,由hashcode 做特定位運算得到hash 碼;
2.利用hash 碼定位找到數組下標,得到鍊錶的鏈首;
3.遍歷鍊錶尋找有沒有相同的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 --- Jerry
123 --- 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 的效率。