Java中equals方法与==和hashCode的区别

时间:2023-01-05 16:09:07

java中的数据类型,可分为两类:
1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean,他们之间的比较,应用双等号(==),比较的是他们的值
2.复合数据类型(类)
  当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。


JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,其实现如下:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. public boolean equals(Object anObject) {   
  2.     if (this == anObject)  
  3.         return true;   
  4. }  

由源码可以看到equals方法是直接使用“==”来实现的,所以这个方法的初始行为是比较对象的内存地址。


关于Objec类的equals方法的特点
A)自反性:X.equals(X)应该返回true
B)对称性:X.equals(y)为true,那么y.equals(X)也为true。
C)传递性:X.equals(y)为true,并且y.equals(Z)为true,那么X.equals(Z)也应该为true。
D)一致性:X.equals(y)的第一次调用为true,那么X.equals(y)的第二次、第三次、第n次调用也应该是true,如果在比较之间没有修改X和y。
E)对于非空引用X,X.equals(null)返回false。

但在一些类库当中equals方法被覆盖掉了,如String,Integer,Date在这些类重写了equals方法,重写的equals方法也要满足上面的5条原则。
比如String类中的equals方法实现如下:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. public boolean equals(Object anObject) {   
  2.     if (this == anObject) {   
  3.         return true;   
  4.     }  
  5.       
  6.     if (anObject instanceof String) {   
  7.         String anotherString = (String)anObject;   
  8.         int n = count;   
  9.         if (n == anotherString.count) {   
  10.             char v1[] = value;   
  11.             char v2[] = anotherString.value;   
  12.             int i = offset;   
  13.             int j = anotherString.offset;   
  14.             while (n-- != 0) {   
  15.                 if (v1[i++] != v2[j++])   
  16.                     return false;   
  17.             }   
  18.             return true;   
  19.         }   
  20.     }   
  21.     return false;   
  22. }   
由上面的源码我们发现String类型的数据在比较时先比较两个String对象的地址是否相同,如果地址相同直接返回true;如果地址不相同,则会继续比较String对象的值,如果值相等则返回true,值不相等则返回false。

总结如下:

对数据进行equals比较时:

如果没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同;

如果重写了equals方法的情况下,比如String、Integer、Date这些类,他们之间的比较是基于值的,值相同返回 true,否则返回false。

还有一些自己的定义的类,也可以重写equals方法(和hashCode方法),根据自己的需要来实现equals的逻辑。(参见本博文最后的程序实例,student类重写了这两个方法)

equals方法和“==”的区别暂时讲到这里


———————————华丽的分割线———————————


接下来说一下equals方法和hashCode方法的区别。

equals()反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值

hashCode()是对象或变量通过哈希算法计算出的哈希值

之所以有hashCode方法,是因为在批量的对象比较中,hashCode要比equals来得快,很多集合都用到了hashCode,比如HashTable


首先equals()和hashcode()这两个方法都是从object类中继承过来的,

equals()是对两个对象的地址值进行的比较(即比较引用是否相同)。
hashCode()是一个本地方法,它的实现是根据本地机器相关的。

在object类中,hashCode定义如下:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. public native int hashCode();  
Object类中的hashCode方法比较的是对象的地址(引用地址)。

当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double等这些类都是覆盖了hashcode()方法的。

例如在String类中定义的hashcode()方法如下:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. public int hashCode() {    
  2.     int h = hash;    
  3.     if (h == 0) {    
  4.         int off = offset;    
  5.         char val[] = value;    
  6.         int len = count;    
  7.     
  8.         for (int i = 0; i < len; i++) {    
  9.             h = 31 * h + val[off++];    
  10.         }    
  11.         hash = h;    
  12.     }    
  13.     return h;    
  14. }   

hashCode()方法的特点:

A)在java的一次执行过程中,对于同一个对象的hashCode方法的多次调用,他们应该返回同样的值

B)对于两个对象来说,如果使用equals方法比较返回true,那么这两个对象的hashCode值一定是相同的

C)对于两个对象来说,如果使用equals方法比较返回false,那么这两个对象的hashCode值不要求一定不同,但是如果不同则可以提高应用的性能。

D) 对于Object类来说,不同的Object对象的hashCode值是不同的Object类的hashCode值表示对象的地址


以下几个问题一定要弄明白

1、为什么要重载equal方法?

因为Object的equals方法默认是两个对象的引用的比较,意思就是指向同一内存,地址则相等,否则不相等;

如果你现在需要利用对象里面的值来判断是否相等,则重载equals方法。

2、 为什么重载hashCode方法?

一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode

那么为什么要重载hashCode呢?就HashMap来说,好比HashMap就是一个大内存块,里面有很多小内存块,小内存块里面是一系列的对象,可以利用hashCode来查找小内存块hashCode%size(小内存块数量),所以当equal相等时,hashCode必须相等,而且如果是object对象,必须重载hashCode和equals方法。

3、 为什么equals()相等,hashCode就一定要相等,而hashCode相等,却不要求equals相等?

      1)因为是按照hashCode来访问小内存块,所以hashCode必须相等HashMap获取一个对象是比较key的hashCode相等和equals为true

      2)之所以hashCode相等,却可以equals不等,因为重写hashCode方法时hashCode的计算方式可以根据实际需要自定义的。

           比如ObjectA和ObjectB他们都有属性name,那么hashCode都以name计算,所以hashCode一样,但是两个对象属于不同类型,所以equals为false。

4、 为什么需要hashCode?

     1) 通过hashCode可以很快的查到小内存块。
     2) 通过hashCode比较比equals方法快,当get时先比较hashCode,如果hashCode不同,直接返回false。

equals方法和hashCode方法的区别暂时讲到这里


—————————————华丽的分割线———————————————————


下面举个例子综合说一下上面两个问题:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. public class TestEquals {  
  2.     public static void main(String args[]) {  
  3.         Student stu1 = new Student("张一"6);  
  4.         Student stu2 = new Student("张一"6);  
  5.           
  6.         if (stu1 == stu2)  
  7.         {  
  8.             System.out.println("stu1 == stu2");  
  9.         }else{  
  10.             System.out.println("stu1 != stu2");  
  11.         }  
  12.           
  13.         if (stu1.equals(stu2)) {  
  14.             System.out.println("stu1 equals stu2");  
  15.             System.out.println("stu1的hashCode码:" + stu1.hashCode()   
  16.                              + "\nstu2的hashCode码:" + stu2.hashCode());  
  17.         } else {  
  18.             System.out.println("stu1 not equals stu2");  
  19.         }  
  20.         Student stu11 = new Student("张一"6);      
  21.         Student stu22 = stu11;  
  22.           
  23.         if (stu11 == stu22)  
  24.         {  
  25.             System.out.println("stu11 == stu22");  
  26.         }else{  
  27.             System.out.println("stu11 != stu22");  
  28.         }  
  29.           
  30.         if (stu1.equals(stu2)) {  
  31.             System.out.println("stu11 equals stu22");  
  32.         } else {  
  33.             System.out.println("stu11 not equals stu22");  
  34.         }  
  35.           
  36.         String str1 = "我是小李";  
  37.         String str2 = "我是小李";  
  38.           
  39.         if (str1 == str2){  
  40.             System.out.println("str1 == str2");  
  41.         }else{  
  42.             System.out.println("str1 != str2");  
  43.         }  
  44.           
  45.         if (str1.equals(str2)){  
  46.             System.out.println("str1 equals str2");  
  47.         }else{  
  48.             System.out.println("str1 not equals str2");  
  49.         }  
  50.           
  51.         String str11 = "我是小李";  
  52.         String str22 = new String("我是小李");  
  53.           
  54.         if (str11 == str22){  
  55.             System.out.println("str11 == str22");  
  56.         }else{  
  57.             System.out.println("str11 != str22");  
  58.         }  
  59.           
  60.         if (str11.equals(str22)){  
  61.             System.out.println("str11 equals str22");  
  62.         }else{  
  63.             System.out.println("str11 not equals str22");  
  64.         }  
  65.       
  66.         String str111 = new String("我是小李");  
  67.         String str222 = new String("我是小李");  
  68.           
  69.         if (str111 == str222){  
  70.             System.out.println("str111 == str222");  
  71.         }else{  
  72.             System.out.println("str111 != str222");  
  73.         }  
  74.         if (str111.equals(str222)){  
  75.             System.out.println("str111 equals str222");  
  76.         }else{  
  77.             System.out.println("str111 not equals str222");  
  78.         }  
  79.     }  
  80. }  
  81.   
  82. class Student {  
  83.     private int age;  
  84.     private String name;  
  85.       
  86.     public Student() {  
  87.     }  
  88.   
  89.     public Student(String name, int age) {  
  90.         this.age = age;  
  91.         this.name = name;  
  92.     }  
  93.   
  94.     public int getAge() {  
  95.         return age;  
  96.     }  
  97.   
  98.     public void setAge(int age) {  
  99.         this.age = age;  
  100.     }  
  101.   
  102.     public String getName() {  
  103.         return name;  
  104.     }  
  105.   
  106.     public void setName(String name) {  
  107.         this.name = name;  
  108.     }  
  109.     //生成hashCode  
  110.     public int hashCode() {  
  111.         return (this.name.hashCode() + this.age) * 31;  
  112.     }  
  113.       
  114.     //重写了equals方法,只要student对象中两个属性值相同就返回true  
  115.     public boolean equals(Object obj) {  
  116.         boolean result = false;  
  117.         if (obj == null) {  
  118.             result = false;  
  119.         }  
  120.         if (this == obj) {  
  121.             result = true;  
  122.         }  
  123.   
  124.         if (obj instanceof Student) {  
  125.             Student stu = (Student) obj;  
  126.             if (stu.getName().equals(this.name) && stu.getAge() == (this.age)) {  
  127.                 result = true;  
  128.             }  
  129.   
  130.         } else {  
  131.             result = false;  
  132.         }  
  133.         return result;  
  134.     }  
  135. }  

输出结果如下:

stu1 != stu2
stu1 equals stu2
stu1的hashCode码:24021466
stu2的hashCode码:24021466


stu11 == stu22
stu11 equals stu22


str1 == str2
str1 equals str2


str11 != str22
str11 equals str22


str111 != str222
str111 equals str222

分析:

Student类重写了equals方法和hashCode方法,equals方法比较的是student的name属性和age属性,两者都相等返回true,hashCode方法也是根据student的name属性和age属性计算出来的。

第一种情况:我们new了两个不同的student对象stu1和stu2,所以两者在内存中存放的地址是不一样的,所以stu1!=stu2,但是两个对象的属性值都一样,所以stu1.equals(stu2)=true,同时stu1和stu2的属性都一样,所以计算出来的hashCode也是一样的。在接下来的测试中(没有在代码中写出来,读者可自行测试)凡是equals为true的hashCode的都一样,印证了了对于两个对象来说,如果使用equals方法比较返回true,那么这两个对象的hashCode值一定是相同的

第二种情况:我们先new了一个stu11,然后让stu22指向了stu11,所以两者在内存中存放的地址是一样的,所以stu11==stu22,而且两个对象的属性值都一样,所以stu11.equals(stu22)=true。

第三种情况:这里涉及到字符串缓冲池的概念,String str1 = "我是小李";这句话创建了一个"我是小李"的String对象并放在字符串缓冲池里面;当使用 String str2 = "我是小李"; 这样的表达是创建字符串的时候,程序首先会在这个String缓冲池中寻找相同值的对象,由于str1先被放到了字符串缓冲池中,所以在str2被创建的时候,程序找到了具有相同值的 str1,str2引用str1所引用的对象“我是小李”。所以str1和str2引用的是同一个对象,所以str1 == str2,两者的内容也是一样的,所以str1 equals str2 == true。

第四种情况:String str11 = "我是小李";这句话创建了一个"我是小李"的String对象并放在字符串缓冲池里面;但是str22使用了 new 操作符,他明白的告诉程序:"我要一个新的!不要旧的!"于是一个新的"我是小李"Sting对象被创建在内存中。str11和str22的值相同,但是引用的对象不同即指向的内存地址不同,所以结果是str11!=str2,但是str11 equals str22 为true。

第五种情况:这种情况看起来和第三种情况很相似,但是实质上是不一样的,str111和str222都使用了new操作符,他们俩都重新创建了一个“我是小李”的String对象并引用它,所以是产生了两个存在内存不同位置的“我是小李”String对象,所以str111 !=  str222,但是str111 equals str222 为true。


接下来看一下重写hashCode函数前后的区别

实例1:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. import java.util.HashSet;    
  2. import java.util.Iterator;    
  3.     
  4. public class HashSetTest {    
  5.     
  6.     public static void main(String[] args) {    
  7.         HashSet hs = new HashSet();    
  8.         hs.add(new Student(1"小李"));    
  9.         hs.add(new Student(2"小张"));    
  10.         hs.add(new Student(3"小王"));    
  11.         hs.add(new Student(1"小李"));    
  12.     
  13.         Iterator it = hs.iterator();    
  14.         while (it.hasNext()) {    
  15.             System.out.println(it.next());    
  16.         }    
  17.     }    
  18. }    
  19.     
  20. class Student {    
  21.     int num;    
  22.     String name;    
  23.     
  24.     Student(int num, String name) {    
  25.         this.num = num;    
  26.         this.name = name;    
  27.     }    
  28.     
  29.     public String toString() {    
  30.         return num + ":" + name;    
  31.     }    
  32. }   
输出的结果为: [java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. 1:小李  
  2. 2:小张  
  3. 3:小王  
  4. 1:小李  
set集合有一个原则就是:集合中元素是无序的,但是不能重复,hashset继承自set也一样不能有重复元素。

但是上面的hashset添加了相等的元素,这是不是和hashset的原则违背了呢?答案是:没有。

为什么没有呢,是因为在根据hashcode()对两次建立的new Student(1,“小李”)对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。

为什么会生成不同的哈希码值呢?原因就在于我们自己定义的Student类并没有重写hashcode()和equals()方法,所以继承自object类中的hashcode()方法和equals方法仍然是比较引用对象的内存地址,两个student对象都是通过使用new方法创建的,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样,所以Hashset会把它们当作不同的对象对待。

怎么解决这个问题呢?答案是:在Student类中重写hashcode()和equals()方法。重写hashcode()和equals()方法的Student类如下:

[java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. class Student {    
  2.     int num;    
  3.     String name;    
  4.     
  5.     Student(int num, String name) {    
  6.         this.num = num;    
  7.         this.name = name;    
  8.     }    
  9.     
  10.     public int hashCode() {//重写了Object类的hashCode方法  
  11.         return num * name.hashCode();    
  12.     }    
  13.     
  14.     public boolean equals(Object o) { //重写了Object类的equals方法  
  15.         Student s = (Student) o;    
  16.         return num == s.num && name.equals(s.name);    
  17.     }    
  18.     
  19.     public String toString() {    
  20.         return num + ":" + name;    
  21.     }    
  22. }   
输出的结果为: [java] view plaincopyJava中equals方法与==和hashCode的区别Java中equals方法与==和hashCode的区别
  1. 1:小李  
  2. 2:小张  
  3. 3:小王  
重写了equals方法和hashCode方法后,hashset集合中不存在重复元素了,根据重写的方法,即便两次调用了new Student(1,"小李"),我们在获得对象的哈希码时,两次new的student对象返回的哈希码肯定是一样的,当然根据equals()方法我们也可判断是相同的,所以在向hashset集合中添加时把它们当作重复元素看待了。

重写equals()和hashcode()小结:
      1)重点是equals,重写hashCode只是技术要求(为了提高效率)
      2)为什么要重写equals呢?因为在java的集合框架中,是通过equals来判断两个对象是否相等的
      3)在hibernate中,经常使用set集合来保存相关对象,而set集合是不允许重复的。在向HashSet集合中添加元素时,其实只要重写equals()这一条也可以。但当hashset中元素比较多时,或者是重写的equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率也会非常低,所以引入了hashCode()这个方法,只是为了提高效率,且这是非常有必要的。

引用地址:http://blog.csdn.net/zyrl2012/article/details/23213319