参考文章:从一道面试题彻底搞懂hashCode与equals的作用与区别及应当注意的细节——感谢原博主
先上栗子
Student student1 = new Student(); Student student2 = new Student(); System.out.println(student1.equals(student2)); //false System.out.println(student1 == student2); //false
String a = new String("a"); String b = new String("a"); System.out.println(a == b); //false System.out.println(a.equals(b)); //true
“==“”在比较基本数据类型时比较的是值,而用“==“”号比较两个对象时比较的是两个对象在堆内存中的存放地址。所以,除非是同一个new出来的对象,它们比较的结果是true,否则都为false。
java中的所有类都继承自Object类,Object类中有一个equals()方法,其默认行为与“==”相同,如下
public boolean equals(Object obj) { return (this == obj); }
在没有覆盖equals()方法的情况下,equals()与"=="等效,但在String、Integer等等一些类中,都重写了equals()方法,这时equals()与"=="就不同了。
hashCode()是Object类的另一个方法,该方法通过对象在堆内存中的地址,计算出对象的哈希码值,并将其返回。一般是在集合类中操作,以提高查询效率。
那么问题来了!!!为什么重写了equals就需要重写hashCode呢?实际上这只是一条规范,如果不这样做程序也可以执行,只不过会隐藏bug。如果把对象存储在HashTable,HashSet,HashMap等散列存储结构中,在重写equals后最好也重写hashCode,否则会导致存储数据的不唯一性(存储了两个equals相等的数据)。
来看张元素放入集合的流程图:
由图可知,将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals()方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
看几个测试的例子:
测试一:覆盖equals(Object obj)但不覆盖hashCode(),导致数据不唯一性
import java.util.*; class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Point other = (Point) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } @Override public String toString() { return "x:" + x + ",y:" + y; } } public class HashCodeTest { public static void main(String[] args) { Collection set = new HashSet(); Point p1 = new Point(1, 1); Point p2 = new Point(1, 1); System.out.println(p1.equals(p2)); //比较内容,true System.out.println(p1==p2); //比较堆内存中的地址,false set.add(p1); //此时集合为空,直接加入集合 set.add(p2); //先通过Object类默认的hashCode()比较p2与p1的hashcode值(不等),加入集合 set.add(p1); //因为p1已经存入集合,同一对象返回的hashCode值是一样的,继续判断equals是否返回true,因为是同一对象所以返回true。此时jdk认为该对象已经存在于集合中,所以舍弃。 Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); System.out.println(object); } } }
测试二:覆盖hashCode方法,但不覆盖equals方法,仍然会导致数据的不唯一性
import java.util.*; class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public String toString() { return "x:" + x + ",y:" + y; } } public class HashCodeTest2 { public static void main(String[] args) { Collection set = new HashSet(); Point p1 = new Point(1, 1); Point p2 = new Point(1, 1); System.out.println(p1.equals(p2));//false System.out.println(p1==p2); //false set.add(p1); //集合为空,直接存入集合 set.add(p2); //p1和p2的hashCode相等,所以继续判断equals是否相等,因为这里没有覆盖equals,默认使用'=='来判断,所以这里equals返回false,jdk认为是不同的对象,所以将p2存入集合 set.add(p1); //因为p1已经存入集合,同一对象返回的hashCode值是一样的,并且equals返回true。此时jdk认为该对象已经存在于集合中,所以舍弃 Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); System.out.println(object); } } }
测试三:同时覆盖equals()和hashCode(),数据唯一
import java.util.*; class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Point other = (Point) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public String toString() { return "x:" + x + ",y:" + y; } } public class HashCodeTest4 { public static void main(String[] args) { Collection set = new HashSet(); Point p1 = new Point(1, 1); Point p2 = new Point(1, 1); System.out.println(p1.equals(p2)); //true System.out.println(p1==p2); //false set.add(p1); //此时集合为空,直接加入集合 set.add(p2); //p1与p2的hashCode相等,equals也相等,所以丢弃 set.add(p1); //同一对象,丢弃 Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); System.out.println(object); } } }
所以,要想保证元素的唯一性,必须同时覆盖hashCode和equals才行
注意:在HashSet中插入同一个元素(hashCode和equals均相等)时,会被舍弃,而在HashMap中插入同一个Key(Value不同)时,原来的元素会被覆盖。
测试四:同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。
import java.util.*; class Point { private int x; private int y; public Point(int x, int y) { super(); this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + x; result = prime * result + y; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Point other = (Point) obj; if (x != other.x) return false; if (y != other.y) return false; return true; } @Override public String toString() { return "x:" + x + ",y:" + y; } } public class HashCodeTest3 { public static void main(String[] args) { Collection set = new HashSet(); Point p1 = new Point(1, 1); Point p2 = new Point(1, 2); set.add(p1); set.add(p2); //下面的操作会修改p2的hashcode值 p2.setX(10); p2.setY(10); set.remove(p2); //按照以前的hashcode值查找,发现为空,jdk认为该对象不在集合中,所以不会进行删除操作。然而用户以为该对象已经被删除,导致该对象长时间不能被释放,造成内存泄露 Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object object = iterator.next(); System.out.println(object); } } }
总结:
1.若重写了equals(Object obj)方法,则有必要重写hashCode()方法。
2.若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。
3.若两个对象equals(Objectobj)返回false,则hashCode()不一定返回不同的int数。
4.若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true。
5.若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false。
6.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。