【问题标题】:Possible bug in java.util.Set API [closed]java.util.Set API 中可能存在的错误 [关闭]
【发布时间】:2012-11-24 12:22:49
【问题描述】:

java.util.Set API 状态:

集合不包含元素对 e1 和 e2 使得 e1.equals(e2)

但据我了解TreeSet 使用Comparable/Comparator 来确定 e1 和 e2 是否重复。我错过了什么吗?

【问题讨论】:

    标签: java collections javadoc comparator


    【解决方案1】:

    如果compareToequals 一致(应该如此),那么TreeSet 使用compareTo 还是equals 来确定相等性并不重要。来自 JavaDoc:

    请注意,由集合维护的顺序(无论是否显式 提供了比较器)必须与equals一致,如果它是 正确实现 Set 接口。 (见可比较或比较 用于与等于一致的精确定义。)就是这样 因为 Set 接口是根据 equals 操作定义的, 但是 TreeSet 实例使用它的执行所有元素比较 compareTo(或比较)方法,因此两个元素被视为相等 通过这种方法,从集合的角度来看,它们是相等的。这 一个集合的行为是明确定义的,即使它的顺序是不一致的 等于;它只是不遵守集合的一般合同 界面。

    这个程序将打印'true',即使equals 总是返回false。但错误在于 compareToequals 对于 A 不一致,并且不是 TreeSet 中的错误。

    class A implements Comparable<A> {
        public int compareTo(A a) {
            return 0;
        }
    
        public boolean equals(Object other) {
            return false;
        }
    
        public static void main(String[] args) {
            TreeSet<A> set = new TreeSet<A>();
            set.add(new A());
            System.out.println(set.contains(new A()));
        }
    }
    

    【讨论】:

    • 很奇怪,假设你有一个 Dog 类,并且你想按重量分类你的狗,然后按身高。是否违法? dog1.equals(dog2) 是否有意义?
    • @EvgeniyDorofeev 您可以按体重和身高比较狗,但如果还有其他属性,那么任何比较器/可比较物也应该考虑到它们,因此对于那些满足以下条件的狗,它不会返回 0不相等。
    • 好吧,让我们来看看人——没有人是平等的,平等只是无关紧要的。根据你的说法,当你想比较人时,你违反了一些合同。
    • 如果没有对象是相等的,那么比较器只能返回 -1 或 1。可比较对象总是与等于一起定义的,你不能只是将它们分开。一个可比较的说法是两个人是平等的,这与平等的说法是一样的。
    【解决方案2】:

    TreeSet 使用比较器。你知道它实际上是通过使用 map 来维护元素的吗?

     public TreeSet() {
        this(new TreeMap<E,Object>());
     }
    

    正如你所说集合不包含元素对 e1 和 e2 使得 e1.equals(e2)。这就是 Comparator 有equals 方法的原因。

    无论我们将如何使用Set,它都取决于Comparator。如果我们使用自己的比较器,我们可以将Set 用作List,但通常我们不会这样做。如果我们在TreeSet中添加自己的Object,在初始化TreeSet时必须通过Comparator或者object必须由Comparable实现

    可比
    具有与另一个对象比较自身能力的可比较对象

    比较器
    用于比较两个不同的对象。

    public int compare(Employee o1, Employee o2) {
        // if sort by name
        return o1.getName().compareTo(o2.getName());
     }
    
    
     public boolean equals(Object obj) {
            // equals with ID
            Employee e = (Employee)obj;
            return this.getId().equals(e.getId());
     }
    

    这里是Java Sorting: Comparator vs Comparable 一个教程。

    【讨论】:

      【解决方案3】:

      你的假设是错误的; TreeSet 不使用 Comparable/Comparator 来确定 e1 是否等于 e2。 Equals 方法是 Object 类的一部分,用于确定集合的两个元素是否相同。即 e1.equals(e2)

      但是 Comparable/Comparator 接口用于判断一个元素是否大于、等于或小于其他元素。这在排序期间使用。因此,您唯一需要确保的是 equals 和 compareto 方法是一致的。

      所以比较两个对象使用equals方法,排序时使用comparator/comparable

      编辑 下面是 JDK 6 的方法定义; AbstractSet 的 equals 方法。 TreeSet 扩展了 AbstractSet

          public boolean equals(Object obj)
          {
          if(obj == this)
              return true;
          if(!(obj instanceof Set))
              return false;
          Collection collection = (Collection)obj;
          if(collection.size() != size())
              return false;
          try
          {
              return containsAll(collection);
          }
          catch(ClassCastException classcastexception)
          {
              return false;
          }
          catch(NullPointerException nullpointerexception)
          {
              return false;
          }
          }
      

      http://docs.oracle.com/javase/6/docs/api/java/util/AbstractSet.html

      【讨论】:

      • TreeSet 委托给 TreeMap,它似乎在通常使用 equals 的地方使用 compareTo。请参阅docjar.com/html/api/java/util/TreeMap.java.html中的 getEntry
      • TreeSet的哪个方法调用了TreeMap?
      • 每个方法都调用 TreeMap。 TreeSet 被实现为忽略其值的 TreeMap。
      • 同意TreeSet内部使用Map;但它调用 AbstractSet 的 equals 方法进行相等性检查。
      • equals 方法用于比较两个 Set,而不是 Set 中的两个项目。那里的 containsAll 调用仍然对每个项目使用 compareTo。