热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一个问题完整解决过程记录

有时候一些东西不经意听见了,并不当回事,回头就忘了。人总是这么健忘!学习hebernate的时候,看到一句话,对于需要映射到数据库中的实体类需要满足几个要求,其中有一个是说对于重写

  有时候一些东西不经意听见了,并不当回事,回头就忘了。人总是这么健忘!

  学习hebernate的时候,看到一句话,对于需 要映射到数据库中的实体类需要满足几个要求,其中有一个是说对于重写了equals重写后要重写hashcode。相信这是一句老生常谈的话了,大家都不 陌生。当时我也一扫而过。后来看到一个短片说怎样验证一个COOKIE有效性也提到了hash算法。于是就想验证一下之前的两个重写。

  当时随手就写了一段代码(没有用快捷键),如下:

 1 public class hash {
 2     public static void main(String[] args) {
 3     
 4         HashSet hs = new HashSet();
 5         Student stu1 = new Student("zhang");
 6         Student stu2 = new Student("zhang");
 7         
 8         hs.add(stu1);
 9         hs.add(stu2);
10         System.out.println(hs.size());
11     }
12 }
13 
14 class Student {
15     String name;
16 
17     Student( String name) {
18         this.name = name;
19     }
20     
21 //    @Override
22 //    public int hashCode() {
23 //        // TODO Auto-generated method stub
24 //        final int INDEX = 12345;
25 //        return name.hashCode()*INDEX;
26 //    }假若不覆盖hashcode方法会怎样啦!将会发现两个对象stu1和stu2我们期望是同一个,不用存进来了,但是在hash值这一关就没有过,new对象时默认以对象的内存地址映射hash值,也就是new了两个对象的hash值是不同的,
27 
28     //因此我们需要覆盖hashcode(),让它按我们的期望来返回hash值
29     
30     public int hashcode() {
31         
32         final int INDEX = 12345;
33         return name.hashCode()*INDEX;
34     }
35     
36     
37     public boolean equals(Student stu ) {
38         
39         if(this.name.equals(stu.name)){
40             return true;
41         }
42         return false;
43     }
44 }

   我期望是结果是set中只插入一个,但结果总是两个,搞得我都有点怀疑set插入的判断过程了,难道不是像记忆中的那样首先判断hash值,如果相等就 判断equals,若equals相等就是说明是同一个,就不插入吗!郁闷了半天搞得。后来我说打印一下hash值看一下啦,结果吓我一大跳,怎么有两 个.hashcode()方法,难道是我眼花吗,再仔细一看,打自己耳光的心都有,怎么可以将hashCode()写成hashcode(),赶紧改过 来,但还是不对,问题又在哪里啦,这不是坑吗,短短的几行代码,为什么就是不能按我的想法来啦,郁闷。一共就两个方法,那在看看是不是equals啦,好 吧,懒得自己写了,还是用快捷键了,一个alt+shift+s,覆盖equals看看,结果又让我不爽了,我的刚才的那个equals怎么就是 Student参数啦,好吧,对自己无语了(说到这里就顺带说一下overload和override,前者是在同一个类中根据参数的不同重载,后面的是 子类覆盖父类的方法,除了方法体里的东西不一样外,其余的都一样)。赶快修改过来,再运行,出现了自己想要的结果。

 1 public class hash {
 2     public static void main(String[] args) {
 3     
 4         HashSet hs = new HashSet();
 5         Student stu1 = new Student("zhang");
 6         Student stu2 = new Student("zhang");
 7         
 8         hs.add(stu1);
 9         hs.add(stu2);
10         System.out.println(hs.size());
11     }
12 }
13 
14 class Student {
15     String name;
16 
17     Student( String name) {
18         this.name = name;
19     }
20     
21 //    @Override
22 //    public int hashCode() {
23 //        // TODO Auto-generated method stub
24 //        final int INDEX = 12345;
25 //        return name.hashCode()*INDEX;
26 //    }假若不覆盖hashcode方法会怎样啦!将会发现两个对象stu1和stu2我们期望是同一个,不用存进来了,但是在hash值这一关就没有过,new对象时默认以对象的内存地址映射hash值,也就是new了两个对象的hash值是不同的,
27 
28     //因此我们需要覆盖hashcode(),让它按我们的期望来返回hash值
29     @Override
30     public int hashCode() {
31         // TODO Auto-generated method stub
32         final int INDEX = 12345;
33         return name.hashCode()*INDEX;
34     }
35     
36     @Override
37     public boolean equals(Object obj) {
38         // TODO Auto-generated method stub
39         Student stu =(Student)obj;
40         if(this.name.equals(stu.name)){
41             return true;
42         }
43         return false;
44     }
45 }

   下面就说说如果我们不覆盖hashcode会是什么后果啦。按照我们的想法,stu1和stu2有同样的名字是同一个人,我们希望不要同时都放到set 中,但是我们如果不覆盖hashcode,在new对象的时候就会将对象的内存地址映射成hash值,就会导致两个对象拥有不同的hash值,当然这不是 我们想要的结果,所以我们需要覆盖上诉的两个方法次啊是完整的。

  写到这里或者事情已经完美的解决了,但是再来看看下面这种情况就会发现原来上面说的完全不是那么一回事。真是让人头大啊!

 1 public class HashcodeTest {
 2     public static void main(String[] args) {
 3         HashSet set1 = new HashSet();
 4         Person p1 = new Person();
 5         Person p2 = p1;
 6         p1.setName("mike");
 7         set1.add(p1);
 8         set1.add(p2);
 9         System.out.println(set1.size());
10     }
11 }
12 class Person{
13     String name;
14     public String getName() {
15         return name;
16     }
17     public void setName(String name) {
18         this.name = name;
19     }
20     
21     @Override
22     public int hashCode() {
23         // TODO Auto-generated method stub
24         final int INDEX = 12345;
25         return name.hashCode()*INDEX;
26     }
27     
28     public boolean equals(Object obj) {
29         // TODO Auto-generated method stub
30         Person p = (Person)obj;
31         
32         if(name.equals(p.getName())){
33             return false;
34         }else {
35             return true;
36         }
37     }
38 }

   --------------------------------重点------------------------------------------------------------------------------

  按照我们上面说的,这里应该会添加两个进去才对啊,但是结果出乎我们的预料,这里真的只加入了一个。好吧,明明我的hashCode和equals全都 是好好地写的,那问题出在哪里了。有人会说你不用红色的标出来了嘛,但那不是根本的问题,那只是产生这个问题的表象原因。为了解决这个问题,我在第7行处 加上断点,一步一步的调试跟踪代码(话说虽然平时总会用到断点调试,但都是F6的,很少去看过源码,因为觉得看源码是一件好高大上的事情,我等小辈还 是.....),但这次是真的需要去看源码了,当我调试到这个地方的时候,那个地方啦,请看如下图:蓝色的部分就是这次的重点。当代吗运行到这里的时候我 觉得问题好像出在这里了(当然不是源码错了,要不那还了得),当源码里的变量我看不了,怎么办啦,于是我把这句话拿出来了,写在我自己程序中。

技术分享

 1 public class hash {
 2     public static void main(String[] args) {
 3     
 4         HashSet hs = new HashSet();
 5         Student stu1 = new Student("zhang");
 6         Student stu2 = new Student("zhang");
 7         stu2 = stu1;
 8         System.out.println(stu1.hashCode());
 9         System.out.println(stu2.hashCode());
10         
11         stu1.me(stu2);
12         
13         if(hs.add(stu1)){
14             System.out.println("ok");
15         }
16         if(hs.add(stu2)){
17             System.out.println("ok");
18         }
19         System.out.println(hs.size());
20     }
21 }
22 
23 class Student {
24     String name;
25 
26     Student( String name) {
27         this.name = name;
28     }
29     
30 //    @Override
31 //    public int hashCode() {
32 //        // TODO Auto-generated method stub
33 //        final int INDEX = 12345;
34 //        return name.hashCode()*INDEX;
35 //    }假若不覆盖hashcode方法会怎样啦!将会发现两个对象stu1和stu2我们期望是同一个,不用存进来了,但是在hash值这一关就没有过,new对象时默认以对象的内存地址映射hash值,也就是new了两个对象的hash值是不同的,
36 
37     //因此我们需要覆盖hashcode(),让它按我们的期望来返回hash值
38     @Override
39     public int hashCode() {
40         // TODO Auto-generated method stub
41         final int INDEX = 12345;
42         return name.hashCode()*INDEX;
43     }
44     public void me(Student stu){
45         if (stu.hashCode() == hashCode() && ((this == stu)|| this.equals(stu))) {
46                System.out.println("yes");
47         }
48     }
49     @Override
50     public boolean equals(Object obj) {
51         // TODO Auto-generated method stub
52         Student stu =(Student)obj;
53         if(this.name.equals(stu.name)){
54             return false;
55         }
56         return true;
57     }
58 }

  其他部分都是一样的,只是我添加了一个me()方法,方法中就是从源码里面拿出来的一句话。当我注释掉  stu2 = stu1;的时候不会输出yes,不注释的时候会输出yes。再来仔细看看这句话if (stu.hashCode() == hashCode() && ((this == stu)|| this.equals(stu))),里面有三个判断。好了,这里就是重点,我想要纠正一下自己上面说的一句话,这句话也是我在好多网上看到的“首先判断hash值,如果相等就判断equals,若equals相等就是说明是同一个,就不插入吗!”事实就摆在眼前,除了判断上面说的hash值和equals外,还另外有判断==(即引用是不是相等的)。这点是很重要的,上面添加 stu2 = stu1;不能成功加入进去的原因就是“=="在判断两者引用的时候返回true了,即使后面返回false也没用。

  我 想到这里问题才算正真的解决了,或许有点小题大做,或许是自讨没趣,也许上面我说的在别人看来就没这么一回事,用了hsahCode和equals这么多 年了,还用你来说。但是我只觉得这是我从发现问题一直到最后解决了这个问题的完整过程,我觉得这个过程是有意义的。并且在期间我还发下了一个长期理解错误 的地方,hashset添加不重复的元素除了判断hashCode的返回值和equals外还会判断两者的引用。我想这点就足够了。

一个问题完整解决过程记录


推荐阅读
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 本文介绍了 Confluence 6 中使用的其他 Cookie,这些 Cookie 主要用于存储产品的基本持久性和用户偏好设置,以提升用户体验。 ... [详细]
  • 蒜头君的倒水问题(矩阵快速幂优化)
    蒜头君将两杯热水分别倒入两个杯子中,每杯水的初始量分别为a毫升和b毫升。为了使水冷却,蒜头君采用了一种特殊的方式,即每次将第一杯中的x%的水倒入第二杯,同时将第二杯中的y%的水倒入第一杯。这种操作会重复进行k次,最终求出两杯水中各自的水量。 ... [详细]
  • 经过一年的思考,我发现自己对开发的兴趣并不浓厚,而对算法研究则更加热衷。本文将探讨开发与算法之间的本质差异,并分享我的未来学习计划。 ... [详细]
  • 本文介绍了Java编程语言的基础知识,包括其历史背景、主要特性以及如何安装和配置JDK。此外,还详细讲解了如何编写和运行第一个Java程序,并简要介绍了Eclipse集成开发环境的安装和使用。 ... [详细]
  • Manacher算法详解:寻找最长回文子串
    本文将详细介绍Manacher算法,该算法用于高效地找到字符串中的最长回文子串。通过在字符间插入特殊符号,Manacher算法能够同时处理奇数和偶数长度的回文子串问题。 ... [详细]
  • malloc 是 C 语言中的一个标准库函数,全称为 memory allocation,即动态内存分配。它用于在程序运行时申请一块指定大小的连续内存区域,并返回该区域的起始地址。当无法预先确定内存的具体位置时,可以通过 malloc 动态分配内存。 ... [详细]
  • 本文介绍了多种开源数据库及其核心数据结构和算法,包括MySQL的B+树、MVCC和WAL,MongoDB的tokuDB和cola,boltDB的追加仅树和mmap,levelDB的LSM树,以及内存缓存中的一致性哈希。 ... [详细]
  • Python多线程详解与示例
    本文介绍了Python中的多线程编程,包括僵尸进程和孤儿进程的概念,并提供了具体的代码示例。同时,详细解释了0号进程和1号进程在系统中的作用。 ... [详细]
  • 本文详细介绍了Linux系统中用于管理IPC(Inter-Process Communication)资源的两个重要命令:ipcs和ipcrm。通过这些命令,用户可以查看和删除系统中的消息队列、共享内存和信号量。 ... [详细]
  • A*算法在AI路径规划中的应用
    路径规划算法用于在地图上找到从起点到终点的最佳路径,特别是在存在障碍物的情况下。A*算法是一种高效且广泛使用的路径规划算法,适用于静态和动态环境。 ... [详细]
  • NX二次开发:UFUN点收集器UF_UI_select_point_collection详解
    本文介绍了如何在NX中使用UFUN库进行点收集器的二次开发,包括必要的头文件包含、初始化和选择点集合的具体实现。 ... [详细]
  • 本文介绍了如何在 ASP.NET 中设置 Excel 单元格格式为文本,获取多个单元格区域并作为表头,以及进行单元格合并、赋值、格式设置等操作。 ... [详细]
  • 网络爬虫的规范与限制
    本文探讨了网络爬虫引发的问题及其解决方案,重点介绍了Robots协议的作用和使用方法,旨在为网络爬虫的合理使用提供指导。 ... [详细]
author-avatar
雨爱艳6688
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有