有关java中equals()与hashCode()的探讨

参考文章:从一道面试题彻底搞懂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判断不相等,直接将该元素放入到集合中,否则不放入。

看几个测试的例子:

测试一:覆盖equalsObject 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中插入同一个元素(hashCodeequals均相等)时,会被舍弃,而在HashMap中插入同一个KeyValue不同)时,原来的元素会被覆盖。

测试四:同一对象在执行期间若已经存储在集合中,则不能修改影响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数,则equalsObject obj)不一定返回true

5.两个对象hashCode()返回不同int数,则equalsObject obj)一定返回false

6.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。



猜你喜欢

转载自blog.csdn.net/danielanton/article/details/80517390