Java学习从菜鸟变大鸟之一 hashCode()和equals()的本质区别和联系

来源:互联网 发布:java d 系统参数 编辑:程序博客网 时间:2024/05/01 07:42

http://blog.csdn.net/lishehe/article/details/18839495

在学习Java,根据视频做实例的过程中,对equalshashcode两个方法理解稍微深刻一点,主要是它们两个很容易混淆,容易出错,自己又通过网上的资料学习,和大家分享

equals()方法


equalsObject类提供的方法之一,众所周知,每一个java类都继承自Object类,

所以说每一个对象都有equals这个方法。而我们在用这个方法时却一般都重写这个方法,why?



先看一个Object类中equals()方法的源代码:

 

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">public boolean equals(Object obj) {   
  2.     return (this == obj);   
  3. }  
  4. </span>  


 

    从这个方法中可以看出,只有当一个实例等于它本身的时候,equals()才会返回true值。通俗地说,此时比较的是两个引用是否指向内存中的同一个对象,也可以称做是否实例相等。而我们在使用equals()来比较两个指向值对象的引用的时候,往往希望知道它们逻辑上是否相等,而不是它们是否指向同一个对象——这就是我们通常重写这个方法的原因。


[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">Strings1 = new String(“kvill”),String s2 = new String(“kvill”);s1.equals(s2)为ture,</span>  



       说明String类中已经重写了equals()方法,如果不重写equals()方法,那么s1.equals(s2)默认比较两个对象所指向的内存地址是否相同,返回值必然为false。


    当然接下来我们要改写equals()方法,必须要遵守通用约定。来自java.lang.Object的规范,equals方法实现了等价关系,以下是要求遵循的5点:


1.自反性:对于任意的引用值x,x.equals(x)一定为true。 

2.对称性:对于任意的引用值x 和 y,当x.equals(y)返回true时,y.equals(x)也一定返回true。 

3.传递性:对于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返回true。

4. 一致性:对于任意的引用值x 和y,如果用于equals比较的对象信息没有被修改,多次调用x.equals(y)要么一致地返回true,要么一致地返回false。

5.非空性:对于任意的非空引用值x,x.equals(null)一定返回false。


hashCode()方法


hashcode()这个方法也是从object类中继承过来的,在object类中定义如下:

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">public native int hashCode(); </span>  


    hashCode()返回该对象的哈希码值,该值通常是一个由该对象的内部地址转换而来的整数,它的实现主要是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。


    必须铭记:在每个重写了equals方法的类中,你必须也要重写hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)的集合类结合在一起正常运行。 

     hashCode()的返回值和equals()的关系如下:

     如果x.equals(y)返回“true”,那么xyhashCode()必须相等。 

     如果x.equals(y)返回“false”,那么xyhashCode()有可能相等,也有可能不等。


 

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">public class TestEquals {  
  2.     public static void main(String args[]) {  
  3.         Student s1 = new Student("张一"6);  
  4.         Student s2 = new Student("张一"6);  
  5. if (s1.equals(s2)) {  
  6.             System.out.println("相同  s1的代码:" + s1.hashCode() + "  s2的代码:"  
  7.                     + s2.hashCode());  
  8.         } else {  
  9.             System.out.println("不相同");  
  10.         }  
  11.     }  
  12. }  
  13. class Student {  
  14.     private int age;  
  15.     private String name;  
  16.       
  17.     public Student() {  
  18.     }  
  19. public Student(String name, int age) {  
  20.         this.age = age;  
  21.         this.name = name;  
  22.     }  
  23. public int getAge() {  
  24.         return age;  
  25.     }  
  26. public void setAge(int age) {  
  27.         this.age = age;  
  28.     }  
  29. public String getName() {  
  30.         return name;  
  31.     }  
  32. public void setName(String name) {  
  33.         this.name = name;  
  34.     }  
  35. public int hashCode() {  
  36.         return (this.name.hashCode() + this.age) * 31;  
  37.     }  
  38. public boolean equals(Object obj) {  
  39.         boolean result = false;  
  40.         if (obj == null) {  
  41.             result = false;  
  42.         }  
  43.         if (this == obj) {  
  44.             result = true;  
  45.         }  
  46. if (obj instanceof Student) {  
  47.             Student stu = (Student) obj;  
  48.             if (stu.getName().equals(this.name) && stu.getAge() == (this.age)) {  
  49.                 result = true;  
  50.             }  
  51. else {  
  52.             result = false;  
  53.         }  
  54.         return result;  
  55.     }  
  56. }  
  57. </span>  


详细分析


equals()是判读两个Set是否相等[前提是equals()在类中被覆盖]。==决定引用值是否指向同一对象。


      1、当向集合set中增加对象时,首先计算要增加对象的hashCode码,根据该值来得到一个位置来存放当前的对象,当在该位置没有一个对象存在的话,那么集合set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,再进行一次散列,将该对象放到散列后计算出的新地址里,如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。


      2、当重写equals方法时,必须要重写hashCode方法。在java的集合中,判断两个对象是否相等的规则是:

            1),判断两个对象的hashCode是否相等

              如果不相等,认为两个对象也不相等,完毕 ; 如果相等,转入2

            2),判断两个对象用equals运算是否相等

            如果不相等,认为两个对象也不相等

            如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

            可见hashcode()相等时,equals()方法也可能不等。



[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">public static void main(String args[]){  
  2.   
  3. String s1=new String("zhaoxudong"); //此语句创建了两个对象,一个是字符串对象“zhaoxudong”(存放于栈中的字面量),另一个是new后在堆中产生的对象。详细见下面的四.4  
  4.   
  5. String s2=new String("zhaoxudong");  
  6.   
  7. //上述两条语句一共是产生了三个对象,因为栈中只有产生了一个对象。  
  8.   
  9. System.out.println(s1==s2);//false  
  10.   
  11. System.out.println(s1.equals(s2));//true  
  12.   
  13. System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() ,指向同一内存的引用  
  14.   
  15. System.out.println(s2.hashCode()); //equals和hashCode方法只用于两个对象的比较和容器中,与对象的创建没有关系  
  16.   
  17. Set hashset=new HashSet();  
  18.   
  19. hashset.add(s1);  
  20.   
  21. hashset.add(s2); /*在添加s1,s2时, hashset认为s1和s2是相等的,所以让s2覆盖了s1;*/  
  22.   
  23. Iterator it=hashset.iterator();  
  24.   
  25.             while(it.hasNext()){  
  26.   
  27.              System.out.println(it.next());  
  28.   
  29.             } //最后在while循环的时候只打印出了一个”zhaoxudong”。 </span>  

 



这是因为String类已经重写了equals()方法和hashcode()方法。


但是看下面的程序:

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">public class HashSetTest {  
  2.   
  3.    public static void main(String[] args)   {  
  4.   
  5.                  HashSet hs=new HashSet();  
  6.   
  7.                  hs.add(new Student(1,"zhangsan"));  
  8.   
  9.                  hs.add(new Student(2,"lisi"));  
  10.   
  11.                  hs.add(new Student(3,"wangwu"));  
  12.   
  13.                  hs.add(new Student(1,"zhangsan"));  
  14.   
  15.                  Iterator it=hs.iterator();  
  16.   
  17.                  while(it.hasNext()){  
  18.   
  19.                         System.out.println(it.next());  
  20.   
  21.                  } } }  
  22.   
  23. class Student {  
  24.   
  25.      int num;  
  26.   
  27.      String name;  
  28.   
  29.      Student(int num,String name) {  
  30.   
  31.                 this.num=num;  
  32.   
  33.                  this.name=name; }  
  34.   
  35.      public String toString() { return num+":"+name; }  
  36.   
  37.            }      </span>  


输出结果为:

                   1:zhangsan

                   1:zhangsan

                   3:wangwu

                   2:lisi



为什么hashset添加了相等的元素呢?


       这是不是和hashset的原则违背了呢?回答:没有因为在根据hashcode()对两次建立newStudent(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。那么为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重新自己的hashcode()和equals()方法

       所以在比较时,是继承的object类中的hashcode()方法,它是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样。那么怎么解决这个问题呢?


原因是:在Student类中重新hashcode()和equals()方法。


[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 18px;">例如:  
  2.   
  3. class Student{  
  4.   
  5. int num;  
  6.   
  7. String name;  
  8.   
  9. Student(int num,String name){  
  10.   
  11.             this.num=num;  
  12.   
  13.             this.name=name; }  
  14.   
  15. public int hashCode(){ //重写hashCode的方法  
  16.   
  17.             return num*name.hashCode(); }  
  18.   
  19. public boolean equals(Object o) {  
  20.   
  21.             Student s=(Student)o;  
  22.   
  23.             return num==s.num && name.equals(s.name); //&&的优先级比==低,所以前面不必加括号  
  24.   
  25. }  
  26.   
  27. public String toString(){return num+":"+name; }  
  28.   
  29. } </span>  


      根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,根据重写的方法hashcode(),获得的哈希码肯定是一样的。所以运行修改后的程序时,我们会看到重复元素的问题已经消除。

总结

这块知识比较容易出错,理解一定要深刻,多多的实践会对原理与定义理解的更加的深刻,To Do,To Do……,祝各位朋友,新年快乐哈!



 

1 0
原创粉丝点击