黑马程序员技术交流社区

标题: 关于equals和Comparable的问题,希望大神给个明确解释! [打印本页]

作者: shdosh    时间: 2015-8-20 21:09
标题: 关于equals和Comparable的问题,希望大神给个明确解释!
在TreeSet集合的时候我不太清楚equals和实现Comparable接口中compareTo方法判断元素会不会出现矛盾的情况。

我在网上看过这个问题他们的说法是http://blog.csdn.net/bluishglc/article/details/20212599这个地址的说法是:这是一个非常基础的问题,但是实际编程中还是比较容易被忽视而导致一些看似奇怪的bug,本文对该问题进行一个小结。
我们知道,Set集合的维护的元素是唯一的,不会出现两个一样的元素,这是通过元素的equals和hashCode方法来判定的。而对于TreeSet来说,它本身除了是一个Set集合,同时还会依据一个Comparator或是Comparable接口对元素进行排序。我们就以Comparable的compareTo方法为例,当这个方法返回0是表示的也是“两个元素相等”,那么这就有可能与equals方法产生冲突,那么TreeSet是如何处理compareTo和equals方法两者之间的关系呢?

先不管两者发生冲突时的处理方法,作为良好的编程实践,我们应该首先保证equals,hashCode和compareTo三者的行为是一致的,这样才不会出现怪异的问题。接下来我们看一下当equals和compareTo行为不一致时TreeSet是如何处理的。从TreeSet文档上我们可以找到以下一段描述:
假定使用 Comparator c 将满足 (a.equals(b) && c.compare(a, b) != 0) 的两个元素 a 和 b 添加到一个空 TreeSet 中,则第二个 add 操作将返回 true(树 set 的大小将会增加),因为从树 set 的角度来看,a 和 b 是不相等的,即使这与 Set.add 方法的规范相反。
在Comparable的文档上我们可以找到另一种描述:
如果将两个键 a 和 b 添加到没有使用显式比较器的有序集合中,使 (!a.equals(b) && a.compareTo(b) == 0),那么第二个 add 操作将返回 false(有序集合的大小没有增加),因为从有序集合的角度来看,a 和 b 是相等的。
很明确,两段说明从一正一反两个方向描述了一个一致的原则:是否能加入一个有序集合是由equals方法决定的,但是equals为true,compare不为0的元素加入到集合中后,在排序上会显得怪异。

下面看我的程序:
  1. class TreeSetDemo
  2. {
  3.         public static void main(String[] args)
  4.         {
  5.                 TreeSet ts = new TreeSet();
  6.                 /*
  7.                 ts.add("cba");
  8.                 ts.add("aaa");
  9.                 ts.add("bca");
  10.                 ts.add("Dbcd");
  11.                
  12.                 Iterator it = ts.iterator();
  13.                
  14.                 while(it.hsNext())
  15.                 {
  16.                         System.out.println(it.next());
  17.                 }
  18.                 */

  19.                 ts.add(new Student("lisi02",22));
  20.                 ts.add(new Student("lisi02",22));
  21.                 ts.add(new Student("lisi09",19));
  22.                 ts.add(new Student("lisi01",40));
  23.                 ts.add(new Student("lisi09",19));
  24.                 Iterator it = ts.iterator();

  25.                 while(it.hasNext())
  26.                 {
  27.                         Student stu = (Student)it.next();
  28.                         System.out.println(stu.getName()+"...."+stu.getAge());
  29.                 }
  30.         }
  31. }


  32. class Student implements Comparable//该接口强制让学生具备比较性。
  33. {
  34.         private String name;
  35.         private int age;

  36.         Student(String name,int age)
  37.         {
  38.                 this.name = name;
  39.                 this.age = age;
  40.         }
  41.          @Override
  42.          public int hashCode() {  

  43.                  System.out.println("In hashCode.....");
  44.             return age;  
  45.      }  
  46.          @Override
  47.         public boolean equals(Object obj){
  48.                 System.out.println("in here..........");
  49.                 if(!(obj instanceof Student))
  50.                         throw new RuntimeException("不是学生对象");
  51.                 Student s = (Student)obj;
  52.                 System.out.println("in here..........");
  53.                 return this == s;
  54.         }
  55.         public int compareTo(Object obj)
  56.         {
  57.                 //return 1;//(03)怎么存就怎么取。
  58.                
  59.                 if(!(obj instanceof Student))
  60.                         throw new RuntimeException("不是学生对象");
  61.                 Student s = (Student)obj;

  62.                 System.out.println(this.name+"....compareto...."+s.name);
  63.                 if(this.age>s.age)
  64.                         return 1;
  65.                 if(this.age==s.age)
  66.                 {
  67.                         return this.name.compareTo(s.name);
  68.                 }
  69.                 return -1;
  70.                
  71.         }
  72.         public String getName()
  73.         {
  74.                 return name;
  75.         }
  76.         public int getAge()
  77.         {
  78.                 return age;
  79.         }
  80. }
复制代码

运行结果:

可以看到,TreeSet集合根本没有调用hashCode方法和equals方法,只是 通过实现Comparable的compareTo方法比较的,而且我认为TreeSet底层用的二叉树的存储方式,也不需要hashCode算法来进行存储,hashCode方法是为了给数据分配哈希域。
大家怎么看??

作者: pengbeilin    时间: 2015-8-20 22:31

作者: 史柯    时间: 2015-8-20 22:34
我这么说吧。
equals方法是HashSet,HashMap保证唯一的方法。
实现Comparable接口中compareTo方法是TreeSet和TreeMap保证唯一和排序的方法。
是不同的结构。
HashSet不调用compare方法,TreeSet不调用equals方法。
所以不会相互影响。
作者: sven556677    时间: 2015-8-20 23:23
楼主想的太多,楼上正解。
你想啊,他treeSet要排序,你equals函数只比较等于不等于,那我要排序我得知道是大于还是小于啊,我当然要用compareTo不用equals啊。但是hashSet/map的作用是啥?如果有很多数据望着一存,通过hash可以快速找到地址,就比如我是管理监狱的狱卒,我就通过监狱号码(hashCode)找罪犯,谁犯事我就弄谁,那我只要知道没有编号相同的罪犯就行了(通过equals比啊),至于谁的编号比谁的编号大,这关我嘛事,所以我不用compareTo。
PS:也许用学生的学号做例子比较文雅点。
作者: wx_HWRW5aF7    时间: 2015-8-20 23:36
点add方法看看源码
作者: shdosh    时间: 2015-8-21 19:35
谢谢各位的回复。
作者: chensheng06    时间: 2015-8-21 21:57
hashCode和equals方法只是用来保证HashSet的元素的唯一性的
再说 equals 和 campareTo的返回值类型也不一样啊
equals 只能返回 true或者false 只能判断等不等
campareTo 是能返回 -1  0  1 三个值的,要排序肯定是要进行大小比较的。





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2