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入门到精通教程
查看: 410|回复: 0

[集合学习]HashSet中加入自定义的类的对象

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-31 23:56:00 | 显示全部楼层 |阅读模式
    这个话题还是从一个有问题的代码中引申出来的,原代码如下: import java.util.*;
    class TreeSetTest
    {
         public static void main(String[] args)
         {
             HashSet hs=new HashSet();
             Student st1=new Student(1,"zhao1");     
             Student st2=new Student(1,"zhao1");     
             hs.add(st1);
             hs.add(st2);
             System.out.println(hs);
         }
    }
    class Student   
    {
         public Student(int num,String name)
         {
             this.num=num;
             this.name=name;
         }
         public int hashCode()
         {
             return new Integer(num).hashCode();
         }
         public boolean equals(Student st)
         {
             if (name==st.name) return true;
             else return false;
         }
         public String toString()
         {
             return "student "+num+" name:"+name;
         }
         int num;
         String name;
    }  为什么st1和st2两个对象内容完全一样,却还能插入到一个set中呢,set不是不能有重复的对象吗?  这段程序有两个主要问题,就要先从Java中两个面向对象的基本含义说起了:
       [blockquote]
        JAVA中的重载overload:
    只要是一个类以及其父类里有的两个函数有相同的名字但是不同的参数列表 (包括参数类型,参数个数,参数顺序3项中的一项或多项)。重载可以在单个类或者两个具有继承关系的类中出现。 是实现类的多态性的一种重要方式。  
       
    JAVA中的覆盖override
    覆盖只会在类继承的时候才会出现,覆盖要求两个函数的名字和参数列表都完全一样。
       [/blockquote] 在HashSet判断是不是重复元素时是使用了equals方法,不过请注意自定义的这个类实际继承了Object类,而Object类中equals方法的定义如下:
       [blockquote]
        public boolean equals(Object o)  
       [/blockquote] 这么说,这段程序中定义的equals方法是对Object中的equals方法的重载,而不是覆盖,那么在HashSet判断重复元素时,实际调用的就是Object.equals 方法,自然是true。 所以该程序第一个需要修改的地方就是equals方法:我们要的是覆盖不是重载,为了防止这样问题,可以加上annotation让Eclipse自己去判断。  
    public boolean equals(Object st)
         {
             Student tempStudent= (Student) st;
             if (name==tempStudent.name) return true;
             else return false;
         }  另外,该程序段在自定义类的hashCode方法和equals并不一致,前者是用num作为hashCode方法的依据,而后者是用name作为判断是不是相同的依据。  1)利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的,在 hashCode中仅在两个对象有着相同hashCode()的时候才会调用equals方法去比较,因为hashset内部采用对某个数字n进行取余的方式对哈希码进行区域划分,也就是说即使哈希码不同,他们也可能被划分在同一个区域。在添加数据时,首先计算hashcode(String 对象的哈希码根据以下公式计算: s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 注:使用 int 算法,这里 s 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希值为 0)),发现在一个区域的才会使用equals方法去一一比较同一区域的对象是否相同,否则直接插入。 |           |            | |  区域1  |   区域2  |  …… |           |            | 这个区域在实现时采用链表的方法。 当调用了 HashSet 的 add 方法存放对象 obj , HashSet 会首先调用 obj 的 hasCode 方法得到该对象的哈希码, HashSet 会使用一个算法把它的哈希码转换成一个数组下标,该下标“标记”了 obj 的位置。如果这个位置上的链表中没有元素,那么就把 obj 对象添加到链表上。如果这个位置上的链表中已经有了元素,则遍历这个链表,调用 obj 的 equals 方法,判断 obj 是否和其中的某个元素重复,如果没有重复的元素,那么就将 obj 添加到链表上;如果有重复的元素,则不会讲 obj 对象存入 HashSet 中。  也就是说,根据哈希表的定义,为了保障相同的对象被放到相同的哈希区域,则必须满足条件:有equals() 返回true=> hashCode() 返回true。 因为先判断的是hashCode的值,换句话说,equals的值为true是hashCode值为true 的充分非必要条件。这样的话,就不会出现两个实际相同的对象,仅仅因为不在同一个哈希区域而被错误的加入到哈希集合中的情况发生了。  2)并且由于链表的缺点在于查询速度慢,所以在我们定义自己的hashCode()和equals()时,为了照顾到哈希表的性能 ,也要遵循“equals返回false时,hashCode也为false” 综合上述1)和2)两点,若hashCode方法和equals不一致则hashCode()和equals()结果没有任何关系,也就是说 equals返回true时,hashcode()也可能是false的,这个与哈希表定义中不允许相同的元素的定义不符合,也不符合哈希表性能优化的需要。 得出的结论是:建议hashCode和equals方法的判断依据最好是一个,也就是所谓的两个方法兼容。  注意:当一个对象被存储进Hashset中以后,就不能修改这个对象中那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进 hashset对象的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去检索hashset集合,也将返回找不到对象的结果,这会导致无法从hashset集合中单独删除当前对象,从而造成内存泄露。  实例代码如下: package testhashcode;
    /**
      * @author gnuhpc
      *         email: warmbupt@gmail.com
      *         blog:  http://blog.csdn.net/gnuhpc
      * @date 2010-1-13
      */

    import java.util.*;

    class TreeSetTest
    {
         public static void main(String[] args)
         {
             HashSet<Student> hs=new HashSet<Student>();
             Student st1=new Student(1,"zhao");   
             Student st2=new Student(2,"qian");
             Student st3=new Student(3,"sun");
             hs.add(st1);
             hs.add(st2);
             hs.add(st3);
             System.out.println(hs);
             st1.num=4; //可以试着注释掉这一行看一看结果
             hs.remove(st1);
             System.out.println(hs);
         }
    }
    class Student  
    {
         public Student(int num,String name)
         {
             this.num=num;
             this.name=name;
         }
         public int hashCode()
         {
             return new Integer(num).hashCode();
         }
         
         @Override
         public boolean equals(Object st)
         {
             Student tempStudent= (Student) st;
             if (num==tempStudent.num) return true;
             else return false;
         }
         
         public String toString()
         {
             return "student "+num+" name:"+name;
         }
         int num;
         String name;
    }

       
         
         
          
          

            
          

            
          
         
       

      


    源码下载:http://file.javaxxz.com/2014/10/31/235559875.zip
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2025-2-25 19:08 , Processed in 0.700781 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

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