A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© Jim-剣◆﹏ 高级黑马   /  2013-11-29 13:55  /  4084 人查看  /  11 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 Jim-剣◆﹏ 于 2013-11-30 00:22 编辑

今早回复一帖子,发现一问题
我先说说我对hashSet对去除重复元素的看法,如有不对,大伙给我指出来
hashSet集合添加重复对象的识别方法是:
首先根据对象的hashCode值进行比较
如果hashCode值不同,则认为是不同的对象,直接添加进hashSet集合中
如果hashCode值相同,则继续用对象的equals方法比较,结果返回true则认为对象相同,添加失败,返回false则认为对象不同,添加成功


如果添加的对象没有覆写hashCode方法,那么此时的对象则是继承Object类的hashCode方法(经过查阅百度和API文档,得知Object中的hashCode方法是一个抽象方法,但是最终的实现是更具对象的内存地址值转化成一个int类型的值),hashCode值可以直观看成是对象内存地址值
如果添加的对象没有覆写equals方法,那么此时对象也是继承Object类的equals方法,进查阅API和源码,Object类中的equals方法内部实现是通过“=”比较两个对象的内存地址来实现的。
运行以下代码

  1. import java.util.*;

  2. class ReflectPoint {

  3.         private int x;
  4.         private int y;

  5.         ReflectPoint(int x, int y) {
  6.                 this.x = x;
  7.                 this.y = y;
  8.         }

  9.         @Override
  10.         public boolean equals(Object obj) {
  11.                 System.out.println("hashCode一致,调用equals");

  12.          return false;
  13.                
  14.         }


  15. class CollectionDemo {

  16.         public static void main(String[] args) {
  17.                 myTestFunc();
  18.         }

  19.         static void myTestFunc() {

  20.                 Collection collections = new HashSet();
  21.                 ReflectPoint pt1 = new ReflectPoint(3, 3);
  22.                 ReflectPoint pt2 = new ReflectPoint(5, 5);
  23.                 ReflectPoint pt3 = new ReflectPoint(3, 3);
  24.                 ReflectPoint pt4 = pt1;

  25.                 System.out.println(collections.add(pt1));
  26.                 System.out.println(collections.add(pt2));
  27.                 System.out.println(collections.add(pt3));
  28.                
  29.                 System.out.println(collections.add(pt1));
  30.                 System.out.println(collections.add(pt4));
  31.                
  32.                 System.out.println(collections.size());

  33.         }
  34. }
复制代码

的对象覆写了equals,并且无论对象内容是否相同,固定返回false值
期待的结果应该是
添加:true
添加:true
添加:true
添加:true
添加:true
元素个数:5
但是运行结果为
添加:true
添加:true
添加:true
添加:false
添加:false
元素个数:3

也就是说,pt1和pt4不能被重复添加,
问题一:为什么呢?进行添加操作的时候,会检查对象的hashCode值和equals,pt4和pt1的hashCode值固然相等(输出pt4和pt1的hashCode值验证过),继而就应该执行equals(),而且equals必定返回false,所以,应该认为pt1和pt4是不同的对象,成功添加,这里为什么添加失败?

问题二::没有执行equals,因为没有执行输出语句System.out.println("hashCode一致,调用equals"),两对象的hashCode值一致,为啥不执行equals?以下修改并覆写hashCode方法
  1. class ReflectPoint {

  2.         private int x;
  3.         private int y;

  4.         ReflectPoint(int x, int y) {
  5.                 this.x = x;
  6.                 this.y = y;
  7.         }

  8.         @Override
  9.         public boolean equals(Object obj) {
  10.                 System.out.println("hashCode一致,调用equals");
  11.          return false;
  12. }

  13. @Override
  14.         public int hashCode() {
  15.                 // TODO Auto-generated method stub
  16.          return (this.x + this.y) * 31;
  17.         }
  18. }
复制代码






此时打印的结果为:
添加:true
添加:true
hashCode一致,调用equals
添加:true
hashCode一致,调用equals
添加:false
hashCode一致,调用equals
添加:false
元素个数:3
明显这次在hashCode值相同的情况下,程序调用了对象的equals方法
问题三:对象仍然添加失败,这是为何?

评分

参与人数 1技术分 +1 收起 理由
贺奕凯 + 1

查看全部评分

11 个回复

正序浏览
冯晓骏 发表于 2013-11-29 23:44
那也就是说JVM调用的还是Object原来的equals方法?

肯定不是啊!调用复写过的方法,返回的也是false,但是添加不到hashset里面。
我以前看过一个10年左右的帖子,上面做的实验和楼主差不多,不同的是,他那程序强制返回false的话就可以添加相同元素,具体记不清了。。。
总之,那就算个BUG吧,所以,才让程序员复写equals和hashcode必须遵从规范。
再深入的话,我也不清楚了。。。
回复 使用道具 举报
本帖最后由 明月几时有 于 2013-11-30 01:15 编辑
冯晓骏 发表于 2013-11-29 23:44
那也就是说JVM调用的还是Object原来的equals方法?

张孝祥老师的反射有讲到这个问题,简单的说最后的两个元素与前面元素比较引用名,发现相等,添加不进去,后面元素的hashcode和equals就不判断了
回复 使用道具 举报
Clare0621 发表于 2013-11-29 17:59
参见api文档Object类中equals方法介绍如下:
---------------------------------------------------------- ...

大神你是对的,一阵见血,一解释就懂,这么细节的东西还真的不容易察觉
回复 使用道具 举报
Clare0621 发表于 2013-11-29 23:02
api文档里面的介绍是编写equals方法的规范,不遵从那个规范,实现equals方法也就没有任何意义。楼主复写 ...

那也就是说JVM调用的还是Object原来的equals方法?
回复 使用道具 举报
冯晓骏 发表于 2013-11-29 19:13
一阵见血,但是这个意想不到的错误究竟是什么呢,程序也没有报错,就算是重写有错,编译器和JVM都无动于 ...

api文档里面的介绍是编写equals方法的规范,不遵从那个规范,实现equals方法也就没有任何意义。楼主复写的equals方法没语法错误,因此,编译是不会失败的。
回复 使用道具 举报
看来还真是应该再去多读读源代码和API的啊
回复 使用道具 举报
原来是equals方法的自反性。。
回复 使用道具 举报
Clare0621 发表于 2013-11-29 17:59
参见api文档Object类中equals方法介绍如下:
---------------------------------------------------------- ...

一阵见血,但是这个意想不到的错误究竟是什么呢,程序也没有报错,就算是重写有错,编译器和JVM都无动于衷么。。重写有错的时候HashSet比较两个元素是否相当到底是经过怎样的步骤呢,难道是调用原来Object原有的equals方法?
回复 使用道具 举报
这个问题很奇怪,当equals 强制返回false时, equals每次add都会被调用。
但当equals强制返回 true时,equals就没被调用了: 有同学过来解释下么?
equals 强制返回false:
添加:true
添加:true
hashCode一致,调用equals
添加:true
hashCode一致,调用equals
添加:false
hashCode一致,调用equals
添加:false
元素个数:3


equals 强制返回true:
true
true
hashCode一致,调用equals
false
false  //根本没调用equals
false  //根本没调用equals
2


评分

参与人数 1技术分 +1 收起 理由
简★零度 + 1

查看全部评分

回复 使用道具 举报
本帖最后由 Clare0621 于 2013-11-29 18:00 编辑

参见api文档Object类中equals方法介绍如下:
--------------------------------------------------------------------------
equalspublic boolean equals(Object obj)指示其他某个对象是否与此对象“相等”。equals 方法在非空对象引用上实现相等关系:
  • 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
  • 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
  • 传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
  • 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 x,x.equals(null) 都应返回 false。
Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。
注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

参数:obj - 要与之比较的引用对象。返回:如果此对象与 obj 参数相同,则返回 true;否则返回 false。另请参见:hashCode(), Hashtable----------------------------------------------------------------------

由自反性性可见,你那样复写equals方法是不符合规范的,所以,在第二次添加pt1和添加pt4时,实际上是执行了pt1.equals(pt1);强制返回false,出现了意想不到的错误。。。




评分

参与人数 1技术分 +1 收起 理由
滔哥 + 1

查看全部评分

回复 使用道具 举报
我就顶顶
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马