【问题标题】:Consequence when compareTo() is inconsistent with equals()compareTo() 与 equals() 不一致时的后果
【发布时间】:2013-03-22 14:27:40
【问题描述】:

compareTo() 与一个类的equals() 不一致时,有人可以阐明什么是后果。我已经读到如果Obj1.compareTo(Obj2) = 0,那么Obj1.equals(Obj2) = true 不是强制性的。但如果发生这种情况,后果是什么。谢谢。

【问题讨论】:

  • 除非你做一个
  • @Sam 我是你能不能详细点。
  • 这个问题很模糊。结果是“如果你用两种不同的方式比较它们,你会得到两个不同的答案”——后续的结果取决于你为什么要进行这种比较。
  • 例如,如果你输入像if (Obj1.equals(Obj2) && Obj1.compareTo(Obj2)!= 0){throw new exception();} 这样的行,那么它们不一致会引发异常
  • BigDecimal 是一个不一致的例子。

标签: java


【解决方案1】:

Comparable 的文档对此进行了详细解释:

C 的自然排序被认为与equals 一致当且仅当e1.compareTo(e2) == 0 具有与e1.equals(e2) 相同的布尔值对于每个e1e2 类@987654330 @。请注意,null 不是任何类的实例,e.compareTo(null) 应该抛出 NullPointerException,即使 e.equals(null) 返回 false

强烈建议(尽管不是必需的)自然排序与equals 保持一致。这是因为没有显式比较器的排序集(和排序映射)在与自然顺序与equals 不一致的元素(或键)一起使用时表现“奇怪”。特别是,这样的排序集(或排序图)违反了集合(或图)的一般约定,该约定是根据equals 方法定义的。

例如,如果一个添加两个键 ab 使得 (!a.equals(b) && a.compareTo(b) == 0) 到一个不使用显式比较器的排序集,则第二个添加操作返回 false(以及排序后的大小set 不会增加),因为从排序集的角度来看,ab 是等价的。

几乎所有实现Comparable 的Java 核心类都具有与equals 一致的自然顺序。一个例外是java.math.BigDecimal,它的自然排序等同于具有相同值和不同精度的BigDecimal 对象(例如4.04.00)。

【讨论】:

  • compareToequals 不一致的有用示例:stackoverflow.com/questions/12587896/…
  • @NPE 你能解释一下吗“特别是,这样的有序集合(或有序映射)违反了集合(或映射)的一般合同,它是根据 equals 方法定义的。”举个例子。我无法正确获取它。谢谢。
  • @Trying:来自Set 的文档:集合不包含元素 e1 和 e2 对,使得 e1.equals(e2)。另一方面,SortedSet(它必须履行这个契约,因为它是一个Set)使用compareTo() 而不是equals()。因此,如果您的对象没有始终如一地实现compareTo/equals,而您将它们放在SortedSet 中,您将迫使后者违反合同。
【解决方案2】:

虽然文档说一致性不是强制性的,但最好始终确保这种一致性,因为您永远不知道您的对象是否有一天会出现在 TreeMap / TreeSet 或类似的地方。如果compareTo() 为 2 个不相等的对象返回 0,则所有基于 Tree 的集合都被破坏。

例如,想象一个类 Query,实现一个 SQL 查询,有 2 个字段:

  • tableList:表格列表
  • 参考:使用此类查询的程序列表

如果两个对象的 tableList 相等,则假设它们相等,即 tableList 是该对象的自然键。 hashCode()equals() 只考虑字段tableList

public class Query implements Comparable {
    List<String> tableList;
    List<String> references;

    Query(List<String> tableList, List<String> references) {
        this.tableList = tableList;
        this.references = references;
        Collections.sort(tableList); // normalize
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 53 * hash + Objects.hashCode(this.tableList);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Query other = (Query) obj;
        return Objects.equals(this.tableList, other.tableList);
    }
}

假设我们希望按照引用的数量进行排序。 天真地编写代码会产生一个compareTo() 方法,如下所示:

public int compareTo(Object o) {
    Query other = (Query) o;
    int s1 = references.size();
    int s2 = other.references.size();
    if (s1 == s2) {
        return 0;
    }
    return s1 - s2;
}

这样做似乎没问题,因为相等和排序是在两个单独的字段上完成的,到目前为止一切都很好。

但是,无论何时放入TreeSetTreeMap,都是灾难性的:这些类的实现认为如果compareTo 返回0,则元素相等。在这种情况下,这意味着每个具有相同引用数量的对象确实是“相等”的对象,显然情况并非如此。

更好的compareTo() 方法可能是:

public int compareTo(Object o) {
    Query other = (Query) o;
    // important to match equals!!!
    if (this.equals(other)) {
        return 0;
    }
    int s1 = references.size();
    int s2 = other.references.size();
    if (s1 == s2) {
        return -1; // not 0, they are NOT equal!
    }
    return s1 - s2;
}

【讨论】:

    【解决方案3】:

    一些集合会假设如果两个对象跟在obj1.compareTo(obj2) = 0 之后,那么obj1.equals(obj2) 也是如此。例如:排序TreeSet。 未能满足此逻辑将导致图标一致的集合。看: Comparator and equals().

    【讨论】:

      猜你喜欢
      • 2011-10-10
      • 1970-01-01
      • 2014-08-12
      • 2012-03-26
      • 1970-01-01
      • 2021-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多