Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 480|回复: 0

[默认分类] 为什么要同时重写equals和hashcode

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-3-27 09:20:32 | 显示全部楼层 |阅读模式
    原文地址https://blog.csdn.net/tiantiandjava/article/details/46988461
    原文地址https://blog.csdn.net/lijiecao0226/article/details/24609559
    知乎文章https://www.zhihu.com/question/26872848  这篇直呼文章很重要!!!!-------------------- ==,equals的区别
    首先来区分Java中 ==,equals,hashCode的区别
    ==是运算符,用于比较两个变量是否相等。
    equals,是Objec类的方法,用于比较两个对象是否相等,默认Object类的equals方法是比较两个对象的地址,跟==的结果一样。Object的equals方法如下:
     

      
      
       [java] 
       view plain
        copy
       
         
       
       
      
      
      public boolean equals(Object obj) {  
          return (this == obj);  
      }  



    hashCode也是Object类的一个方法。返回一个离散的int型整数。在集合类操作中使用,为了提高查询速度。(HashMap,HashSet等)
     
     
    有了这三个基础概念,区别就简单了。网上有很多,汇总一下:
    java中的数据类型,可分为两类: 
    1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean 
      他们之间的比较,应用双等号(==),比较的是他们的值。 
    2.复合数据类型(类) 
      当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。 JAVA当中所有的类都是继承于Object这个基类的,在Object中的基类中定义了一个equals的方法,这个方法的初始行为是比较对象的内存地 址,但在一些类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而不再是比较类在堆内存中的存放地址了。
      对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是基于他们在内存中的存放位置的地址值的,因为Object的equals方法也是用双等号(==)进行比较的,所以比较后的结果跟双等号(==)的结果相同。
     
    如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
    如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生相同的整数结果
    从而在集合操作的时候有如下规则:
    将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
    回过来说get的时候,HashMap也先调key.hashCode()算出数组下标,然后看equals如果是true就是找到了,所以就涉及了equals。
     
     
    《Effective Java》书中有两条是关于equals和hashCode的:
    覆盖equals时需要遵守的通用约定: 
      覆盖equals方法看起来似乎很简单,但是如果覆盖不当会导致错误,并且后果相当严重。《Effective Java》一书中提到“最容易避免这类问题的办法就是不覆盖equals方法”,这句话貌似很搞笑,其实想想也不无道理,其实在这种情况下,类的每个实例都只与它自身相等。如果满足了以下任何一个条件,这就正是所期望的结果: 
    类的每个实例本质上都是唯一的。对于代表活动实体而不是值的类来说却是如此,例如Thread。Object提供的equals实现对于这些类来说正是正确的行为。
    不关心类是否提供了“逻辑相等”的测试功能。假如Random覆盖了equals,以检查两个Random实例是否产生相同的随机数序列,但是设计者并不认为客户需要或者期望这样的功能。在这样的情况下,从Object继承得到的equals实现已经足够了。
    超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。大多数的Set实现都从AbstractSet继承equals实现,List实现从AbstractList继承equals实现,Map实现从AbstractMap继承equals实现。
    类是私有的或者是包级私有的,可以确定它的equals方法永远不会被调用。在这种情况下,无疑是应该覆盖equals方法的,以防止它被意外调用:
    @Override 
    public boolean equals(Object o){ 
      throw new AssertionError(); //Method is never called 
    } 

      在覆盖equals方法的时候,你必须要遵守它的通用约定。下面是约定的内容,来自Object的规范[JavaSE6] 
    自反性。对于任何非null的引用值x,x.equals(x)必须返回true。
    对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
    传递性。对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
    一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用该x.equals(y)就会一直地返回true,或者一致地返回false。
    对于任何非null的引用值x,x.equals(null)必须返回false。

      结合以上要求,得出了以下实现高质量equals方法的诀窍: 
    1.使用==符号检查“参数是否为这个对象的引用”。如果是,则返回true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。 
    2.使用instanceof操作符检查“参数是否为正确的类型”。如果不是,则返回false。一般来说,所谓“正确的类型”是指equals方法所在的那个类。 
    3.把参数转换成正确的类型。因为转换之前进行过instanceof测试,所以确保会成功。 
    4.对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回true;否则返回false。 
    5.当编写完成了equals方法之后,检查“对称性”、“传递性”、“一致性”。 
     
    覆盖equals时总要覆盖hashCode 的原因:
      一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。  
    比如将两个对象存入HashSet里面:如果不重写hashCode,还用原来的hashCode就可能判断两个对象的hashCode不相等(其实重写hashCode再判断这两个对象才发现这两个对象的hashCode是相等的),然后因为判断这两个对象的hashCode不相等就会直接将这两个对象存入HashSet,这就导致了实际上存入了这两个相同的对象。

    在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
    如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
    如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生相同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
     
    ========================================================================================================================
    ========================================================================================================================
    ========================================================================================================================
    ========================================================================================================================
     
                                                                                          最好同时重写equals和hashcode的原因
    最近去面试了几家公司,被问到hashCode的作用,虽然回答出来了,但是自己还是对hashCode和equals的作用一知半解的,所以决定把它们研究一下。
    以前写程序一直没有注意hashCode的作用,一般都是覆盖了equals,缺没有覆盖hashCode,现在发现这是埋下了很多潜在的Bug!今天就来说一说hashCode和equals的作用。
           先来试想一个场景,如果你想查找一个集合中是否包含某个对象,那么程序应该怎么写呢?通常的做法是逐一取出每个元素与要查找的对象一一比较,当发现两者进行equals比较结果相等时,则停止查找并返回true,否则,返回false。但是这个做法的一个缺点是当集合中的元素很多时,譬如有一万个元素,那么逐一的比较效率势必下降很快。于是有人发明了一种哈希算法来提高从该集合中查找元素的效率,这种方式将集合分成若干个存储区域(可以看成一个个桶),每个对象可以计算出一个哈希码,可以根据哈希码分组,每组分别对应某个存储区域,这样一个对象根据它的哈希码就可以分到不同的存储区域(不同的桶中)。如下图所示:
                                                
    实际的使用中,一个对象一般有key和value,可以根据key来计算它的hashCode。假设现在全部的对象都已经根据自己的hashCode值存储在不同的存储区域中了,那么现在查找某个对象(根据对象的key来查找),不需要遍历整个集合了,现在只需要计算要查找对象的key的hashCode,然后找到该hashCode对应的存储区域,在该存储区域中来查找就可以了,这样效率也就提升了很多。说了这么多相信你对hashCode的作用有了一定的了解,下面就来看看hashCode和equals的区别和联系。
    在研究这个问题之前,首先说明一下JDK对equals(Object obj)和hashCode()这两个方法的定义和规范:在Java中任何一个对象都具备equals(Object obj)和hashCode()这两个方法,因为他们是在Object类中定义的。 equals(Object obj)方法用来判断两个对象是否“相同”,如果“相同”则返回true,否则返回false。 hashCode()方法返回一个int数,在Object类中的默认实现是“将该对象的内部地址转换成一个整数返回”。 
    下面是官方文档给出的一些说明:
     

      
      
       [java] 
       view plain
        copy
       
         
       
       
      
      
      <span&nbsp;style="font-size:18px;">hashCode&nbsp;的常规协定是:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      在&nbsp;Java&nbsp;应用程序执行期间,在同一对象上多次调用&nbsp;hashCode&nbsp;方法时,必须一致地返回相同的整数,前提是对象上&nbsp;equals&nbsp;比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      如果根据&nbsp;equals(Object)&nbsp;方法,两个对象是相等的,那么在两个对象中的每个对象上调用&nbsp;hashCode&nbsp;方法都必须生成相同的整数结果。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      以下情况不&nbsp;是必需的:如果根据&nbsp;equals(java.lang.Object)&nbsp;方法,两个对象不相等,那么在两个对象中的任一对象上调用&nbsp;hashCode&nbsp;方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      实际上,由&nbsp;Object&nbsp;类定义的&nbsp;hashCode&nbsp;方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是&nbsp;JavaTM&nbsp;编程语言不需要这种实现技巧。)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;
      当equals方法被重写时,通常有必要重写&nbsp;hashCode&nbsp;方法,以维护&nbsp;hashCode&nbsp;方法的常规协定,该协定声明相等对象必须具有相等的哈希码。</span>&nbsp;&nbsp;



    下面是我查阅了相关资料之后对以上的说明做的归纳总结:
    &nbsp;
    1.若重写了equals(Object obj)方法,则有必要重写hashCode()方法。
    2.若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。
    3.若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数。
    4.若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true。
    5.若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false。
    6.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。

    &nbsp;


    想要弄清楚以上六点,先要知道什么时候需要重写equals和hashCode。一般来说涉及到对象之间的比较大小就需要重写equals方法,但是为什么第一点说重写了equals就需要重写hashCode呢?实际上这只是一条规范,如果不这样做程序也可以执行,只不过会隐藏bug。一般一个类的对象如果会存储在HashTable,HashSet,HashMap等散列存储结构中,那么重写equals后最好也重写hashCode,否则会导致存储数据的不唯一性(存储了两个equals相等的数据)。而如果确定不会存储在这些散列结构中,则可以不重写hashCode。但是个人觉得还是重写比较好一点,谁能保证后期不会存储在这些结构中呢,况且重写了hashCode也不会降低性能,因为在线性结构(如ArrayList)中是不会调用hashCode,所以重写了也不要紧,也为后期的修改打了补丁。


    &nbsp;


    下面来看一张对象放入散列集合的流程图:


    &nbsp;

    从上面的图中可以清晰地看到在存储一个对象时,先进行hashCode值的比较,然后进行equals的比较。可能现在你已经对上面的6点归纳有了一些认识。我们还可以通过JDK中得源码来认识一下具体hashCode和equals在代码中是如何调用的。
    HashSet.java&nbsp;
    &nbsp;
      
       
       
        [java]&nbsp;
        view plain
        &nbsp;copy
       
         &nbsp;
         
       
       
       
       <span&nbsp;style="font-size:18px;">&nbsp;&nbsp;public&nbsp;boolean&nbsp;add(E&nbsp;e)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;map.put(e,&nbsp;PRESENT)==null;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}</span>&nbsp;&nbsp;
      
      

    HashMap.java
    &nbsp;
      
       
       
        [java]&nbsp;
        view plain
        &nbsp;copy
       
         &nbsp;
         
       
       
       
       <span&nbsp;style="font-size:18px;">&nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;V&nbsp;put(K&nbsp;key,&nbsp;V&nbsp;value)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(key&nbsp;==&nbsp;null)&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;putForNullKey(value);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;hash&nbsp;=&nbsp;hash(key.hashCode());&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;indexFor(hash,&nbsp;table.length);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(Entry<K,V>&nbsp;e&nbsp;=&nbsp;table;&nbsp;e&nbsp;!=&nbsp;null;&nbsp;e&nbsp;=&nbsp;e.next)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;k;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(e.hash&nbsp;==&nbsp;hash&nbsp;&&&nbsp;((k&nbsp;=&nbsp;e.key)&nbsp;==&nbsp;key&nbsp;||&nbsp;key.equals(k)))&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;V&nbsp;oldValue&nbsp;=&nbsp;e.value;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.value&nbsp;=&nbsp;value;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.recordAccess(this);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;oldValue;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;modCount++;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addEntry(hash,&nbsp;key,&nbsp;value,&nbsp;i);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;null;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}</span>&nbsp;&nbsp;
      
      




    &nbsp;

    &nbsp;
    最后再来看几个测试的例子吧:
    测试一:覆盖equals(Object obj)但不覆盖hashCode(),导致数据不唯一性
    &nbsp;

      
      
       [java]&nbsp;
       view plain
       &nbsp;copy
       
        &nbsp;
       
       
      
      
      <span&nbsp;style="font-size:18px;">public&nbsp;class&nbsp;HashCodeTest&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Collection&nbsp;set&nbsp;=&nbsp;new&nbsp;HashSet();&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point&nbsp;p1&nbsp;=&nbsp;new&nbsp;Point(1,&nbsp;1);&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point&nbsp;p2&nbsp;=&nbsp;new&nbsp;Point(1,&nbsp;1);&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(p1.equals(p2));&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set.add(p1);&nbsp;&nbsp;&nbsp;//(1)&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set.add(p2);&nbsp;&nbsp;&nbsp;//(2)&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set.add(p1);&nbsp;&nbsp;&nbsp;//(3)&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Iterator&nbsp;iterator&nbsp;=&nbsp;set.iterator();&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(iterator.hasNext())&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;object&nbsp;=&nbsp;iterator.next();&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(object);&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      }&nbsp;&nbsp;
      &nbsp;&nbsp;
      class&nbsp;Point&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;int&nbsp;x;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;int&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Point(int&nbsp;x,&nbsp;int&nbsp;y)&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super();&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;x;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;boolean&nbsp;equals(Object&nbsp;obj)&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(this&nbsp;==&nbsp;obj)&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(obj&nbsp;==&nbsp;null)&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(getClass()&nbsp;!=&nbsp;obj.getClass())&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point&nbsp;other&nbsp;=&nbsp;(Point)&nbsp;obj;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(x&nbsp;!=&nbsp;other.x)&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(y&nbsp;!=&nbsp;other.y)&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;toString()&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;"x:"&nbsp;+&nbsp;x&nbsp;+&nbsp;",y:"&nbsp;+&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;
      }&nbsp;&nbsp;
      </span>&nbsp;&nbsp;





    &nbsp;
    输出结果:
    &nbsp;

      
      
       [java]&nbsp;
       view plain
       &nbsp;copy
       
        &nbsp;
       
       
      
      
      <span&nbsp;style="font-size:18px;">true&nbsp;&nbsp;
      x:1,y:1&nbsp;&nbsp;
      x:1,y:1&nbsp;&nbsp;
      </span>&nbsp;&nbsp;



    原因分析:
    &nbsp;
    (1)当执行set.add(p1)时(1),集合为空,直接存入集合;
    (2)当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,因为没有覆盖hashCode方法,所以jdk使用默认Object的hashCode方法,返回内存地址转换后的整数,因为不同对象的地址值不同,所以这里不存在与p2相同hashCode值的对象,因此jdk默认不同hashCode值,equals一定返回false,所以直接存入集合。
    &nbsp;(3)当执行set.add(p1)时(3),时,因为p1已经存入集合,同一对象返回的hashCode值是一样的,继续判断equals是否返回true,因为是同一对象所以返回true。此时jdk认为该对象已经存在于集合中,所以舍弃。
    &nbsp;
    测试二:覆盖hashCode方法,但不覆盖equals方法,仍然会导致数据的不唯一性
    修改Point类:

      
      
       [java]&nbsp;
       view plain
       &nbsp;copy
       
        &nbsp;
       
       
      
      
      <span&nbsp;style="font-size:18px;">class&nbsp;Point&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;int&nbsp;x;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;int&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Point(int&nbsp;x,&nbsp;int&nbsp;y)&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super();&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;x;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;int&nbsp;hashCode()&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;int&nbsp;prime&nbsp;=&nbsp;31;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;result&nbsp;=&nbsp;1;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;prime&nbsp;*&nbsp;result&nbsp;+&nbsp;x;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;prime&nbsp;*&nbsp;result&nbsp;+&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;result;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;toString()&nbsp;{&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;"x:"&nbsp;+&nbsp;x&nbsp;+&nbsp;",y:"&nbsp;+&nbsp;y;&nbsp;&nbsp;
      &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
      &nbsp;&nbsp;
      }&nbsp;&nbsp;
      </span>&nbsp;&nbsp;



    输出结果:

      
      
       [java]&nbsp;
       view plain
       &nbsp;copy
       
        &nbsp;
       
       
      
      
      <span&nbsp;style="font-size:18px;">false&nbsp;&nbsp;
      x:1,y:1&nbsp;&nbsp;
      x:1,y:1</span>&nbsp;&nbsp;


    &nbsp;
    &nbsp;
    原因分析:
    (1)当执行set.add(p1)时(1),集合为空,直接存入集合;
    (2)当执行set.add(p2)时(2),首先判断该对象(p2)的hashCode值所在的存储区域是否有相同的hashCode,这里覆盖了hashCode方法,p1和p2的hashCode相等,所以继续判断equals是否相等,因为这里没有覆盖equals,默认使用"=="来判断(返回内存地址),所以这里equals返回false,jdk认为是不同的对象,所以将p2存入集合。
    &nbsp;(3)当执行set.add(p1)时(3),时,因为p1已经存入集合,同一对象返回的hashCode值是一样的,并且equals返回true。此时jdk认为该对象已经存在于集合中,所以舍弃。
    &nbsp;

    综合上述两个测试,要想保证元素的唯一性,必须同时覆盖hashCode和equals才行。


    (注意:在HashSet中插入同一个元素(hashCode和equals均相等)时,会被舍弃,而在HashMap中插入同一个Key(Value 不同)时,原来的元素会被覆盖。)


    &nbsp;


    测试三:在内存泄露问题


      
       
       
        [java]&nbsp;
        view plain
        &nbsp;copy
       
         &nbsp;
         
       
       
       
       <span&nbsp;style="font-size:18px;">public&nbsp;class&nbsp;HashCodeTest&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;static&nbsp;void&nbsp;main(String[]&nbsp;args)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Collection&nbsp;set&nbsp;=&nbsp;new&nbsp;HashSet();&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point&nbsp;p1&nbsp;=&nbsp;new&nbsp;Point(1,&nbsp;1);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point&nbsp;p2&nbsp;=&nbsp;new&nbsp;Point(1,&nbsp;2);&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set.add(p1);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set.add(p2);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p2.setX(10);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p2.setY(10);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;set.remove(p2);&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Iterator&nbsp;iterator&nbsp;=&nbsp;set.iterator();&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(iterator.hasNext())&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Object&nbsp;object&nbsp;=&nbsp;iterator.next();&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(object);&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       }&nbsp;&nbsp;
       &nbsp;&nbsp;
       class&nbsp;Point&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;int&nbsp;x;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;private&nbsp;int&nbsp;y;&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;Point(int&nbsp;x,&nbsp;int&nbsp;y)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super();&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;x;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;y;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;int&nbsp;getX()&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;x;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;setX(int&nbsp;x)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.x&nbsp;=&nbsp;x;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;int&nbsp;getY()&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;y;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;void&nbsp;setY(int&nbsp;y)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this.y&nbsp;=&nbsp;y;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;int&nbsp;hashCode()&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final&nbsp;int&nbsp;prime&nbsp;=&nbsp;31;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;result&nbsp;=&nbsp;1;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;prime&nbsp;*&nbsp;result&nbsp;+&nbsp;x;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;result&nbsp;=&nbsp;prime&nbsp;*&nbsp;result&nbsp;+&nbsp;y;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;result;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;boolean&nbsp;equals(Object&nbsp;obj)&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(this&nbsp;==&nbsp;obj)&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(obj&nbsp;==&nbsp;null)&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(getClass()&nbsp;!=&nbsp;obj.getClass())&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Point&nbsp;other&nbsp;=&nbsp;(Point)&nbsp;obj;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(x&nbsp;!=&nbsp;other.x)&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(y&nbsp;!=&nbsp;other.y)&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;false;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;true;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;@Override&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;String&nbsp;toString()&nbsp;{&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;"x:"&nbsp;+&nbsp;x&nbsp;+&nbsp;",y:"&nbsp;+&nbsp;y;&nbsp;&nbsp;
       &nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;&nbsp;
       &nbsp;&nbsp;
       }&nbsp;&nbsp;
       </span>&nbsp;&nbsp;
      
      

      &nbsp;
      运行结果:


      
       
       
        [java]&nbsp;
        view plain
        &nbsp;copy
       
         &nbsp;
         
       
       
       
       <span&nbsp;style="font-size:18px;">x:1,y:1&nbsp;&nbsp;
       x:10,y:10</span>&nbsp;&nbsp;
      
      

      &nbsp;
      原因分析:


    &nbsp; &nbsp; 假设p1的hashCode为1,p2的hashCode为2,在存储时p1被分配在1号桶中,p2被分配在2号筒中。这时修改了p2中与计算hashCode有关的信息(x和y),当调用remove(Object obj)时,首先会查找该hashCode值得对象是否在集合中。假设修改后的hashCode值为10(仍存在2号桶中),这时查找结果空,jdk认为该对象不在集合中,所以不会进行删除操作。然而用户以为该对象已经被删除,导致该对象长时间不能被释放,造成内存泄露。解决该问题的办法是不要在执行期间修改与hashCode值有关的对象信息,如果非要修改,则必须先从集合中删除,更新信息后再加入集合中。


    &nbsp;


    总结:


    &nbsp; &nbsp;1.hashCode是为了提高在散列结构存储中查找的效率,在线性表中没有作用。


    &nbsp; &nbsp;2.equals和hashCode需要同时覆盖。


    &nbsp; &nbsp;3.若两个对象equals返回true,则hashCode有必要也返回相同的int数。


    4.若两个对象equals返回false,则hashCode不一定返回不同的int数,但为不相等的对象生成不同hashCode值可以提高 哈希表的性能。
    5.若两个对象hashCode返回相同int数,则equals不一定返回true。
    6.若两个对象hashCode返回不同int数,则equals一定返回false。
    &nbsp; &nbsp;7.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。
    &nbsp;

    &nbsp;
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-26 05:35 , Processed in 0.444759 second(s), 53 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表