详解hashCode()和equals()的本质区别和联系

 更新时间:2017年09月29日 16:57:37   转载 作者:李社河  
这篇文章主要介绍了详解hashCode()和equals()的本质区别和联系,本文先对两种方法作了介绍,然后对二者联系进行分析,具有一定参考价值,需要的朋友可以了解下。

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

equals()方法

equals是Object类提供的方法之一,众所周知,每一个java类都继承自Object类,所以说每一个对象都有equals这个方法。而我们在用这个方法时却一般都重写这个方法,why?

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

public boolean equals(Object obj) {  
  return (this == obj);  
} 

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

Strings1 = new String(“kvill”),String s2 = new String(“kvill”);s1.equals(s2)为ture,

   说明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类中定义如下:

public native int hashCode(); 

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

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

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

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

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

public class TestEquals { 
  public static void main(String args[]) { 
    Student s1 = new Student("张一", 6); 
    Student s2 = new Student("张一", 6); 
if (s1.equals(s2)) { 
      System.out.println("相同 s1的代码:" + s1.hashCode() + " s2的代码:" 
          + s2.hashCode()); 
    } else { 
      System.out.println("不相同"); 
    } 
  } 
} 
class Student { 
  private int age; 
  private String name; 
   
  public Student() { 
  } 
public Student(String name, int age) { 
    this.age = age; 
    this.name = name; 
  } 
public int getAge() { 
    return age; 
  } 
public void setAge(int age) { 
    this.age = age; 
  } 
public String getName() { 
    return name; 
  } 
public void setName(String name) { 
    this.name = name; 
  } 
public int hashCode() { 
    return (this.name.hashCode() + this.age) * 31; 
  } 
public boolean equals(Object obj) { 
    boolean result = false; 
    if (obj == null) { 
      result = false; 
    } 
    if (this == obj) { 
      result = true; 
    } 
if (obj instanceof Student) { 
      Student stu = (Student) obj; 
      if (stu.getName().equals(this.name) && stu.getAge() == (this.age)) { 
        result = true; 
      } 
} else { 
      result = false; 
    } 
    return result; 
  } 
} 

详细分析

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

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

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

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

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

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

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

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

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

public static void main(String args[]){ 
String s1=new String("zhaoxudong"); //此语句创建了两个对象,一个是字符串对象“zhaoxudong”(存放于栈中的字面量),另一个是new后在堆中产生的对象。详细见下面的四.4 
String s2=new String("zhaoxudong"); 
//上述两条语句一共是产生了三个对象,因为栈中只有产生了一个对象。 
System.out.println(s1==s2);//false 
System.out.println(s1.equals(s2));//true 
System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() ,指向同一内存的引用 
System.out.println(s2.hashCode()); //equals和hashCode方法只用于两个对象的比较和容器中,与对象的创建没有关系 
Set hashset=new HashSet(); 
hashset.add(s1); 
hashset.add(s2); /*在添加s1,s2时, hashset认为s1和s2是相等的,所以让s2覆盖了s1;*/ 
Iterator it=hashset.iterator(); 
      while(it.hasNext()){ 
       System.out.println(it.next()); 
      } //最后在while循环的时候只打印出了一个”zhaoxudong”。

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

但是看下面的程序:

>public class HashSetTest { 
  public static void main(String[] args)  { 
         HashSet hs=new HashSet(); 
         hs.add(new Student(1,"zhangsan")); 
         hs.add(new Student(2,"lisi")); 
         hs.add(new Student(3,"wangwu")); 
         hs.add(new Student(1,"zhangsan")); 
         Iterator it=hs.iterator(); 
         while(it.hasNext()){ 
            System.out.println(it.next()); 
         } } } 
class Student { 
   int num; 
   String name; 
   Student(int num,String name) { 
        this.num=num; 
         this.name=name; } 
   public String toString() { return num+":"+name; } 
      } 

输出结果为:

          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()方法。

class Student{ 
 
int num; 
 
String name; 
 
Student(int num,String name){ 
 
      this.num=num; 
 
      this.name=name; } 
 
public int hashCode(){ //重写hashCode的方法 
 
      return num*name.hashCode(); } 
 
public boolean equals(Object o) { 
 
      Student s=(Student)o; 
 
      return num==s.num && name.equals(s.name); //&&的优先级比==低,所以前面不必加括号 
 
} 
 
public String toString(){return num+":"+name; } 
 
}

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

总结

这块知识比较容易出错,理解一定要深刻,多多的实践会对原理与定义理解的更加的深刻。有什么问题可以随时留言,小编会及时回复大家的。感谢大家对本站的支持。

相关文章

  • java中unicode和中文相互转换的简单实现

    java中unicode和中文相互转换的简单实现

    下面小编就为大家带来一篇java中unicode和中文相互转换的简单实现。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • Java中的FileInputStream 和 FileOutputStream 介绍_动力节点Java学院整理

    Java中的FileInputStream 和 FileOutputStream 介绍_动力节点Jav

    FileInputStream 是文件输入流,它继承于InputStream。FileOutputStream 是文件输出流,它继承于OutputStream。接下来通过本文给大家介绍Java中的FileInputStream 和 FileOutputStream,需要的朋友可以参考下
    2017-05-05
  • java并发编程之同步器代码示例

    java并发编程之同步器代码示例

    这篇文章主要介绍了java并发编程之同步器代码示例,分享了相关代码,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • Java实现Socket的TCP传输实例

    Java实现Socket的TCP传输实例

    这篇文章主要介绍了Java实现Socket的TCP传输,实例分析了java通过socket实现TCP传输的相关技巧,需要的朋友可以参考下
    2015-05-05
  • Spring Boot学习入门之AOP处理请求详解

    Spring Boot学习入门之AOP处理请求详解

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,下面这篇文章主要给大家介绍了关于Spring Boot学习入门之AOP处理请求的相关资料,需要的朋友可以参考下。
    2017-09-09
  • Spring与Struts整合之让Spring管理控制器操作示例

    Spring与Struts整合之让Spring管理控制器操作示例

    这篇文章主要介绍了Spring与Struts整合之让Spring管理控制器操作,结合实例形式详细分析了Spring管理控制器相关配置、接口实现与使用技巧,需要的朋友可以参考下
    2020-01-01
  • Spring boot 路径映射的实现

    Spring boot 路径映射的实现

    这篇文章主要介绍了spring boot 路径映射的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java判断IP地址为内网IP还是公网IP的方法

    Java判断IP地址为内网IP还是公网IP的方法

    这篇文章主要介绍了Java判断IP地址为内网IP还是公网IP的方法,针对tcp/ip协议中保留的三个私有地址进行判断分析,是比较实用的技巧,需要的朋友可以参考下
    2015-01-01
  • Java针对封装数组的简单复杂度分析方法

    Java针对封装数组的简单复杂度分析方法

    这篇文章主要介绍了Java针对封装数组的简单复杂度分析方法,简单讲述了java封装数组时间复杂度分析的原理、实现方法与操作注意事项,需要的朋友可以参考下
    2020-03-03
  • Java常用字符串方法小结

    Java常用字符串方法小结

    字符串变量是Java与C语言的一大不同之处。Java之中的 String 类和 Stringbuffer 类提供了大量的对字符串操作的方法。String 类适合处理较小的字符串,而Stringbuffer类适合处理大量字符串
    2017-04-04

最新评论