参考文章:从一道面试题彻底搞懂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);
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/e59a414708b6908408eacd646a2ad370.png)
所以,要想保证元素的唯一性,必须同时覆盖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);
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/476afc06e905f1dd5e15fe01234a34a4.png)
总结:
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值的相关信息,否则会导致内存泄露问题。