讲解:为什么重写equals时必须重写hashCode方法
一 :string类型的==和equals的区别:
结论:"=="是判断两个字符串的内存地址是否相等,equals是比较两个字符串的值是否相等,具体就不做扩展了,有兴趣的同学可以去查看相关的博客。
String s1 = new String("java");
String s2 = new String("java");
System.out.println(s1==s2); //false
System.out.println(s1.equals(s2)); //true
在Java中,equals和hashCode方法是Object中提供的两个方法,string类型重写了equals()方法和hashCod()方法,源码如下:
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; } /*返回哈希码,String的哈希码计算方式为s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]*/ public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++]; } hash = h; } return h; }
二:对象equals 和hashcode的关系
结论:当对象的equals()方法被重写时,通常有必要重写 hashCode() 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。如下:
(1)两个对象相等,hashcode一定相等
(2)两个对象不等,hashcode不一定不等
(3)hashcode相等,两个对象不一定相等
(4)hashcode不等,两个对象一定不等
hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但 不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致。
三 :使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()
A.当put元素时:
1.首先根据put元素的key获取hashcode,然后根据hashcode算出数组的下标位置,如果下标位置没有元素,直接放入元素即可。
2.如果该下标位置有元素(即根据put元素的key算出的hashcode一样即重复了),则需要已有元素和put元素的key对象比较equals方法,如果equals不一样,则说明可以放入进map中。这里由于hashcode一样,所以得出的数组下标位置相同。所以会在该 数组位置创建一个链表,后put进入的元素到放链表头,原来的元素向后移动。
B.当get元素时:
根据元素的key获取hashcode,然后根据hashcode获取数组下标位置,如果只有一个元素则直接取出。如果该位置一个链表,则需要调用equals方法遍历链表中的所有元素与当前的元素比较,得到真正想要的对象。
C. 采用hashcode的好处:
如果我们不使用hashcode,直接使用equals比较,那么我们有10w个元素,put值或者get值时要比较10w次,如果先通过hashcode判断后,我们的效率就会提高很多。
我们可以对比实际生活中,如果有一栋教学楼,一楼是1年级,二楼是2年级,以此类推,六楼是6年级,这时我们需要找5年级2班在哪间教室,通过hashcode直接定位到5楼,然后在5楼一间间找就行了,这样避免从一楼一间一间查看。
四.:代码展示,为什么重写equals时必须重写hashCode方法
我们以一个student 学生类来测试重写equals()和重写hashcode()后,map的get()方法返回值结果,让大家清晰了解为什么重写equals时必须重写hashCode方法
package com.sinaif; public class Student { private String name; //姓名 private String sex; //性别 private String idCard;//身份证号 Student(String name,String sex,String idCard){ this.name = name; this.sex = sex; this.idCard = idCard; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getIdCard() { return idCard; } public void setIdCard(String idCard) { this.idCard = idCard; } @Override public boolean equals(Object obj) { // TODO Auto-generated method stub if(obj == null){ return false; } Student obj1 = (Student)obj; if(this.name.equals(obj1.getName()) && this.idCard.equals(obj1.getIdCard())){ return true; } return false; } //@Override public int hashCode() { // TODO Auto-generated method stub String s= this.name + this.idCard; return s.hashCode(); } }
public static void main(String[] args) throws Exception { Map<Student,Student> map = new HashMap<Student,Student>(); Student s1 = new Student("haly","male","110200"); Student s2 = new Student("haly","female","110200"); map.put(s1, s1); System.out.println(map.get(s2)); // 当没有重写equals方法和hashcode方法,打印出null // 当重写equals方法,不重写hashcode方法时,打印出null // 当重写equals方法,并且重写hashcode方法时,打印出com.test.Student@95fb7d6 }